Mark the strings "first" and "sencond" for translation.
[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, 2012 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 int establish P((void));
151 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
152                          char *buf, int count, int error));
153 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
154                       char *buf, int count, int error));
155 void ics_printf P((char *format, ...));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225
226 #ifdef WIN32
227        extern void ConsoleCreate();
228 #endif
229
230 ChessProgramState *WhitePlayer();
231 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
232 int VerifyDisplayMode P(());
233
234 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
235 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
236 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
237 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
238 void ics_update_width P((int new_width));
239 extern char installDir[MSG_SIZ];
240 VariantClass startVariant; /* [HGM] nicks: initial variant */
241 Boolean abortMatch;
242
243 extern int tinyLayout, smallLayout;
244 ChessProgramStats programStats;
245 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
246 int endPV = -1;
247 static int exiting = 0; /* [HGM] moved to top */
248 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
249 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
250 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
251 int partnerHighlight[2];
252 Boolean partnerBoardValid = 0;
253 char partnerStatus[MSG_SIZ];
254 Boolean partnerUp;
255 Boolean originalFlip;
256 Boolean twoBoards = 0;
257 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
258 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
259 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
260 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
261 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
262 int opponentKibitzes;
263 int lastSavedGame; /* [HGM] save: ID of game */
264 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
265 extern int chatCount;
266 int chattingPartner;
267 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
268 char lastMsg[MSG_SIZ];
269 ChessSquare pieceSweep = EmptySquare;
270 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
271 int promoDefaultAltered;
272
273 /* States for ics_getting_history */
274 #define H_FALSE 0
275 #define H_REQUESTED 1
276 #define H_GOT_REQ_HEADER 2
277 #define H_GOT_UNREQ_HEADER 3
278 #define H_GETTING_MOVES 4
279 #define H_GOT_UNWANTED_HEADER 5
280
281 /* whosays values for GameEnds */
282 #define GE_ICS 0
283 #define GE_ENGINE 1
284 #define GE_PLAYER 2
285 #define GE_FILE 3
286 #define GE_XBOARD 4
287 #define GE_ENGINE1 5
288 #define GE_ENGINE2 6
289
290 /* Maximum number of games in a cmail message */
291 #define CMAIL_MAX_GAMES 20
292
293 /* Different types of move when calling RegisterMove */
294 #define CMAIL_MOVE   0
295 #define CMAIL_RESIGN 1
296 #define CMAIL_DRAW   2
297 #define CMAIL_ACCEPT 3
298
299 /* Different types of result to remember for each game */
300 #define CMAIL_NOT_RESULT 0
301 #define CMAIL_OLD_RESULT 1
302 #define CMAIL_NEW_RESULT 2
303
304 /* Telnet protocol constants */
305 #define TN_WILL 0373
306 #define TN_WONT 0374
307 #define TN_DO   0375
308 #define TN_DONT 0376
309 #define TN_IAC  0377
310 #define TN_ECHO 0001
311 #define TN_SGA  0003
312 #define TN_PORT 23
313
314 char*
315 safeStrCpy (char *dst, const char *src, size_t count)
316 { // [HGM] made safe
317   int i;
318   assert( dst != NULL );
319   assert( src != NULL );
320   assert( count > 0 );
321
322   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
323   if(  i == count && dst[count-1] != NULLCHAR)
324     {
325       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
326       if(appData.debugMode)
327       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
328     }
329
330   return dst;
331 }
332
333 /* Some compiler can't cast u64 to double
334  * This function do the job for us:
335
336  * We use the highest bit for cast, this only
337  * works if the highest bit is not
338  * in use (This should not happen)
339  *
340  * We used this for all compiler
341  */
342 double
343 u64ToDouble (u64 value)
344 {
345   double r;
346   u64 tmp = value & u64Const(0x7fffffffffffffff);
347   r = (double)(s64)tmp;
348   if (value & u64Const(0x8000000000000000))
349        r +=  9.2233720368547758080e18; /* 2^63 */
350  return r;
351 }
352
353 /* Fake up flags for now, as we aren't keeping track of castling
354    availability yet. [HGM] Change of logic: the flag now only
355    indicates the type of castlings allowed by the rule of the game.
356    The actual rights themselves are maintained in the array
357    castlingRights, as part of the game history, and are not probed
358    by this function.
359  */
360 int
361 PosFlags (index)
362 {
363   int flags = F_ALL_CASTLE_OK;
364   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
365   switch (gameInfo.variant) {
366   case VariantSuicide:
367     flags &= ~F_ALL_CASTLE_OK;
368   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
369     flags |= F_IGNORE_CHECK;
370   case VariantLosers:
371     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
372     break;
373   case VariantAtomic:
374     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
375     break;
376   case VariantKriegspiel:
377     flags |= F_KRIEGSPIEL_CAPTURE;
378     break;
379   case VariantCapaRandom:
380   case VariantFischeRandom:
381     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
382   case VariantNoCastle:
383   case VariantShatranj:
384   case VariantCourier:
385   case VariantMakruk:
386   case VariantGrand:
387     flags &= ~F_ALL_CASTLE_OK;
388     break;
389   default:
390     break;
391   }
392   return flags;
393 }
394
395 FILE *gameFileFP, *debugFP;
396 char *currentDebugFile; // [HGM] debug split: to remember name
397
398 /*
399     [AS] Note: sometimes, the sscanf() function is used to parse the input
400     into a fixed-size buffer. Because of this, we must be prepared to
401     receive strings as long as the size of the input buffer, which is currently
402     set to 4K for Windows and 8K for the rest.
403     So, we must either allocate sufficiently large buffers here, or
404     reduce the size of the input buffer in the input reading part.
405 */
406
407 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
408 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
409 char thinkOutput1[MSG_SIZ*10];
410
411 ChessProgramState first, second, pairing;
412
413 /* premove variables */
414 int premoveToX = 0;
415 int premoveToY = 0;
416 int premoveFromX = 0;
417 int premoveFromY = 0;
418 int premovePromoChar = 0;
419 int gotPremove = 0;
420 Boolean alarmSounded;
421 /* end premove variables */
422
423 char *ics_prefix = "$";
424 int ics_type = ICS_GENERIC;
425
426 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
427 int pauseExamForwardMostMove = 0;
428 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
429 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
430 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
431 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
432 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
433 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
434 int whiteFlag = FALSE, blackFlag = FALSE;
435 int userOfferedDraw = FALSE;
436 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
437 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
438 int cmailMoveType[CMAIL_MAX_GAMES];
439 long ics_clock_paused = 0;
440 ProcRef icsPR = NoProc, cmailPR = NoProc;
441 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
442 GameMode gameMode = BeginningOfGame;
443 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
444 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
445 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
446 int hiddenThinkOutputState = 0; /* [AS] */
447 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
448 int adjudicateLossPlies = 6;
449 char white_holding[64], black_holding[64];
450 TimeMark lastNodeCountTime;
451 long lastNodeCount=0;
452 int shiftKey; // [HGM] set by mouse handler
453
454 int have_sent_ICS_logon = 0;
455 int movesPerSession;
456 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
457 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
458 Boolean adjustedClock;
459 long timeControl_2; /* [AS] Allow separate time controls */
460 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
461 long timeRemaining[2][MAX_MOVES];
462 int matchGame = 0, nextGame = 0, roundNr = 0;
463 Boolean waitingForGame = FALSE;
464 TimeMark programStartTime, pauseStart;
465 char ics_handle[MSG_SIZ];
466 int have_set_title = 0;
467
468 /* animateTraining preserves the state of appData.animate
469  * when Training mode is activated. This allows the
470  * response to be animated when appData.animate == TRUE and
471  * appData.animateDragging == TRUE.
472  */
473 Boolean animateTraining;
474
475 GameInfo gameInfo;
476
477 AppData appData;
478
479 Board boards[MAX_MOVES];
480 /* [HGM] Following 7 needed for accurate legality tests: */
481 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
482 signed char  initialRights[BOARD_FILES];
483 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
484 int   initialRulePlies, FENrulePlies;
485 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
486 int loadFlag = 0;
487 Boolean shuffleOpenings;
488 int mute; // mute all sounds
489
490 // [HGM] vari: next 12 to save and restore variations
491 #define MAX_VARIATIONS 10
492 int framePtr = MAX_MOVES-1; // points to free stack entry
493 int storedGames = 0;
494 int savedFirst[MAX_VARIATIONS];
495 int savedLast[MAX_VARIATIONS];
496 int savedFramePtr[MAX_VARIATIONS];
497 char *savedDetails[MAX_VARIATIONS];
498 ChessMove savedResult[MAX_VARIATIONS];
499
500 void PushTail P((int firstMove, int lastMove));
501 Boolean PopTail P((Boolean annotate));
502 void PushInner P((int firstMove, int lastMove));
503 void PopInner P((Boolean annotate));
504 void CleanupTail P((void));
505
506 ChessSquare  FIDEArray[2][BOARD_FILES] = {
507     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
508         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
509     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
510         BlackKing, BlackBishop, BlackKnight, BlackRook }
511 };
512
513 ChessSquare twoKingsArray[2][BOARD_FILES] = {
514     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
516     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517         BlackKing, BlackKing, BlackKnight, BlackRook }
518 };
519
520 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
521     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
522         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
523     { BlackRook, BlackMan, BlackBishop, BlackQueen,
524         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
525 };
526
527 ChessSquare SpartanArray[2][BOARD_FILES] = {
528     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
529         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
530     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
531         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
532 };
533
534 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
535     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
538         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
539 };
540
541 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
542     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
543         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
544     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
545         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
546 };
547
548 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
550         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
551     { BlackRook, BlackKnight, BlackMan, BlackFerz,
552         BlackKing, BlackMan, BlackKnight, BlackRook }
553 };
554
555
556 #if (BOARD_FILES>=10)
557 ChessSquare ShogiArray[2][BOARD_FILES] = {
558     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
559         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
560     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
561         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
562 };
563
564 ChessSquare XiangqiArray[2][BOARD_FILES] = {
565     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
566         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
567     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
568         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
569 };
570
571 ChessSquare CapablancaArray[2][BOARD_FILES] = {
572     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
573         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
574     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
575         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
576 };
577
578 ChessSquare GreatArray[2][BOARD_FILES] = {
579     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
580         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
581     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
582         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
583 };
584
585 ChessSquare JanusArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
587         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
588     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
589         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
590 };
591
592 ChessSquare GrandArray[2][BOARD_FILES] = {
593     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
594         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
595     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
596         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
597 };
598
599 #ifdef GOTHIC
600 ChessSquare GothicArray[2][BOARD_FILES] = {
601     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
602         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
603     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
604         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
605 };
606 #else // !GOTHIC
607 #define GothicArray CapablancaArray
608 #endif // !GOTHIC
609
610 #ifdef FALCON
611 ChessSquare FalconArray[2][BOARD_FILES] = {
612     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
613         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
614     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
615         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
616 };
617 #else // !FALCON
618 #define FalconArray CapablancaArray
619 #endif // !FALCON
620
621 #else // !(BOARD_FILES>=10)
622 #define XiangqiPosition FIDEArray
623 #define CapablancaArray FIDEArray
624 #define GothicArray FIDEArray
625 #define GreatArray FIDEArray
626 #endif // !(BOARD_FILES>=10)
627
628 #if (BOARD_FILES>=12)
629 ChessSquare CourierArray[2][BOARD_FILES] = {
630     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
631         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
632     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
633         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
634 };
635 #else // !(BOARD_FILES>=12)
636 #define CourierArray CapablancaArray
637 #endif // !(BOARD_FILES>=12)
638
639
640 Board initialPosition;
641
642
643 /* Convert str to a rating. Checks for special cases of "----",
644
645    "++++", etc. Also strips ()'s */
646 int
647 string_to_rating (char *str)
648 {
649   while(*str && !isdigit(*str)) ++str;
650   if (!*str)
651     return 0;   /* One of the special "no rating" cases */
652   else
653     return atoi(str);
654 }
655
656 void
657 ClearProgramStats ()
658 {
659     /* Init programStats */
660     programStats.movelist[0] = 0;
661     programStats.depth = 0;
662     programStats.nr_moves = 0;
663     programStats.moves_left = 0;
664     programStats.nodes = 0;
665     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
666     programStats.score = 0;
667     programStats.got_only_move = 0;
668     programStats.got_fail = 0;
669     programStats.line_is_book = 0;
670 }
671
672 void
673 CommonEngineInit ()
674 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
675     if (appData.firstPlaysBlack) {
676         first.twoMachinesColor = "black\n";
677         second.twoMachinesColor = "white\n";
678     } else {
679         first.twoMachinesColor = "white\n";
680         second.twoMachinesColor = "black\n";
681     }
682
683     first.other = &second;
684     second.other = &first;
685
686     { float norm = 1;
687         if(appData.timeOddsMode) {
688             norm = appData.timeOdds[0];
689             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
690         }
691         first.timeOdds  = appData.timeOdds[0]/norm;
692         second.timeOdds = appData.timeOdds[1]/norm;
693     }
694
695     if(programVersion) free(programVersion);
696     if (appData.noChessProgram) {
697         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
698         sprintf(programVersion, "%s", PACKAGE_STRING);
699     } else {
700       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
701       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
702       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
703     }
704 }
705
706 void
707 UnloadEngine (ChessProgramState *cps)
708 {
709         /* Kill off first chess program */
710         if (cps->isr != NULL)
711           RemoveInputSource(cps->isr);
712         cps->isr = NULL;
713
714         if (cps->pr != NoProc) {
715             ExitAnalyzeMode();
716             DoSleep( appData.delayBeforeQuit );
717             SendToProgram("quit\n", cps);
718             DoSleep( appData.delayAfterQuit );
719             DestroyChildProcess(cps->pr, cps->useSigterm);
720         }
721         cps->pr = NoProc;
722         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
723 }
724
725 void
726 ClearOptions (ChessProgramState *cps)
727 {
728     int i;
729     cps->nrOptions = cps->comboCnt = 0;
730     for(i=0; i<MAX_OPTIONS; i++) {
731         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
732         cps->option[i].textValue = 0;
733     }
734 }
735
736 char *engineNames[] = {
737   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
738      such as "% engine" / "%s chess program" / "%s machine" - all mening the same thing */
739 N_("first"),
740   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
741      such as "% engine" / "%s chess program" / "%s machine" - all mening the same thing */
742 N_("second")
743 };
744
745 void
746 InitEngine (ChessProgramState *cps, int n)
747 {   // [HGM] all engine initialiation put in a function that does one engine
748
749     ClearOptions(cps);
750
751     cps->which = _(engineNames[n]);
752     cps->maybeThinking = FALSE;
753     cps->pr = NoProc;
754     cps->isr = NULL;
755     cps->sendTime = 2;
756     cps->sendDrawOffers = 1;
757
758     cps->program = appData.chessProgram[n];
759     cps->host = appData.host[n];
760     cps->dir = appData.directory[n];
761     cps->initString = appData.engInitString[n];
762     cps->computerString = appData.computerString[n];
763     cps->useSigint  = TRUE;
764     cps->useSigterm = TRUE;
765     cps->reuse = appData.reuse[n];
766     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
767     cps->useSetboard = FALSE;
768     cps->useSAN = FALSE;
769     cps->usePing = FALSE;
770     cps->lastPing = 0;
771     cps->lastPong = 0;
772     cps->usePlayother = FALSE;
773     cps->useColors = TRUE;
774     cps->useUsermove = FALSE;
775     cps->sendICS = FALSE;
776     cps->sendName = appData.icsActive;
777     cps->sdKludge = FALSE;
778     cps->stKludge = FALSE;
779     TidyProgramName(cps->program, cps->host, cps->tidy);
780     cps->matchWins = 0;
781     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
782     cps->analysisSupport = 2; /* detect */
783     cps->analyzing = FALSE;
784     cps->initDone = FALSE;
785
786     /* New features added by Tord: */
787     cps->useFEN960 = FALSE;
788     cps->useOOCastle = TRUE;
789     /* End of new features added by Tord. */
790     cps->fenOverride  = appData.fenOverride[n];
791
792     /* [HGM] time odds: set factor for each machine */
793     cps->timeOdds  = appData.timeOdds[n];
794
795     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
796     cps->accumulateTC = appData.accumulateTC[n];
797     cps->maxNrOfSessions = 1;
798
799     /* [HGM] debug */
800     cps->debug = FALSE;
801
802     cps->supportsNPS = UNKNOWN;
803     cps->memSize = FALSE;
804     cps->maxCores = FALSE;
805     cps->egtFormats[0] = NULLCHAR;
806
807     /* [HGM] options */
808     cps->optionSettings  = appData.engOptions[n];
809
810     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
811     cps->isUCI = appData.isUCI[n]; /* [AS] */
812     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
813
814     if (appData.protocolVersion[n] > PROTOVER
815         || appData.protocolVersion[n] < 1)
816       {
817         char buf[MSG_SIZ];
818         int len;
819
820         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
821                        appData.protocolVersion[n]);
822         if( (len >= MSG_SIZ) && appData.debugMode )
823           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
824
825         DisplayFatalError(buf, 0, 2);
826       }
827     else
828       {
829         cps->protocolVersion = appData.protocolVersion[n];
830       }
831
832     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
833     ParseFeatures(appData.featureDefaults, cps);
834 }
835
836 ChessProgramState *savCps;
837
838 void
839 LoadEngine ()
840 {
841     int i;
842     if(WaitForEngine(savCps, LoadEngine)) return;
843     CommonEngineInit(); // recalculate time odds
844     if(gameInfo.variant != StringToVariant(appData.variant)) {
845         // we changed variant when loading the engine; this forces us to reset
846         Reset(TRUE, savCps != &first);
847         EditGameEvent(); // for consistency with other path, as Reset changes mode
848     }
849     InitChessProgram(savCps, FALSE);
850     SendToProgram("force\n", savCps);
851     DisplayMessage("", "");
852     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
853     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
854     ThawUI();
855     SetGNUMode();
856 }
857
858 void
859 ReplaceEngine (ChessProgramState *cps, int n)
860 {
861     EditGameEvent();
862     UnloadEngine(cps);
863     appData.noChessProgram = FALSE;
864     appData.clockMode = TRUE;
865     InitEngine(cps, n);
866     UpdateLogos(TRUE);
867     if(n) return; // only startup first engine immediately; second can wait
868     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
869     LoadEngine();
870 }
871
872 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
873 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
874
875 static char resetOptions[] = 
876         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
877         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
878         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
879
880 void
881 FloatToFront(char **list, char *engineLine)
882 {
883     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
884     int i=0;
885     if(appData.recentEngines <= 0) return;
886     TidyProgramName(engineLine, "localhost", tidy+1);
887     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
888     strncpy(buf+1, *list, MSG_SIZ-50);
889     if(p = strstr(buf, tidy)) { // tidy name appears in list
890         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
891         while(*p++ = *++q); // squeeze out
892     }
893     strcat(tidy, buf+1); // put list behind tidy name
894     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
895     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
896     ASSIGN(*list, tidy+1);
897 }
898
899 void
900 Load (ChessProgramState *cps, int i)
901 {
902     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
903     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
904         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
905         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
906         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
907         appData.firstProtocolVersion = PROTOVER;
908         ParseArgsFromString(buf);
909         SwapEngines(i);
910         ReplaceEngine(cps, i);
911         FloatToFront(&appData.recentEngineList, engineLine);
912         return;
913     }
914     p = engineName;
915     while(q = strchr(p, SLASH)) p = q+1;
916     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
917     if(engineDir[0] != NULLCHAR)
918         appData.directory[i] = engineDir;
919     else if(p != engineName) { // derive directory from engine path, when not given
920         p[-1] = 0;
921         appData.directory[i] = strdup(engineName);
922         p[-1] = SLASH;
923     } else appData.directory[i] = ".";
924     if(params[0]) {
925         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
926         snprintf(command, MSG_SIZ, "%s %s", p, params);
927         p = command;
928     }
929     appData.chessProgram[i] = strdup(p);
930     appData.isUCI[i] = isUCI;
931     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
932     appData.hasOwnBookUCI[i] = hasBook;
933     if(!nickName[0]) useNick = FALSE;
934     if(useNick) ASSIGN(appData.pgnName[i], nickName);
935     if(addToList) {
936         int len;
937         char quote;
938         q = firstChessProgramNames;
939         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
940         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
941         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
942                         quote, p, quote, appData.directory[i], 
943                         useNick ? " -fn \"" : "",
944                         useNick ? nickName : "",
945                         useNick ? "\"" : "",
946                         v1 ? " -firstProtocolVersion 1" : "",
947                         hasBook ? "" : " -fNoOwnBookUCI",
948                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
949                         storeVariant ? " -variant " : "",
950                         storeVariant ? VariantName(gameInfo.variant) : "");
951         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
952         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
953         if(q)   free(q);
954         FloatToFront(&appData.recentEngineList, buf);
955     }
956     ReplaceEngine(cps, i);
957 }
958
959 void
960 InitTimeControls ()
961 {
962     int matched, min, sec;
963     /*
964      * Parse timeControl resource
965      */
966     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
967                           appData.movesPerSession)) {
968         char buf[MSG_SIZ];
969         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
970         DisplayFatalError(buf, 0, 2);
971     }
972
973     /*
974      * Parse searchTime resource
975      */
976     if (*appData.searchTime != NULLCHAR) {
977         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
978         if (matched == 1) {
979             searchTime = min * 60;
980         } else if (matched == 2) {
981             searchTime = min * 60 + sec;
982         } else {
983             char buf[MSG_SIZ];
984             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
985             DisplayFatalError(buf, 0, 2);
986         }
987     }
988 }
989
990 void
991 InitBackEnd1 ()
992 {
993
994     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
995     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
996
997     GetTimeMark(&programStartTime);
998     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
999     appData.seedBase = random() + (random()<<15);
1000     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1001
1002     ClearProgramStats();
1003     programStats.ok_to_send = 1;
1004     programStats.seen_stat = 0;
1005
1006     /*
1007      * Initialize game list
1008      */
1009     ListNew(&gameList);
1010
1011
1012     /*
1013      * Internet chess server status
1014      */
1015     if (appData.icsActive) {
1016         appData.matchMode = FALSE;
1017         appData.matchGames = 0;
1018 #if ZIPPY
1019         appData.noChessProgram = !appData.zippyPlay;
1020 #else
1021         appData.zippyPlay = FALSE;
1022         appData.zippyTalk = FALSE;
1023         appData.noChessProgram = TRUE;
1024 #endif
1025         if (*appData.icsHelper != NULLCHAR) {
1026             appData.useTelnet = TRUE;
1027             appData.telnetProgram = appData.icsHelper;
1028         }
1029     } else {
1030         appData.zippyTalk = appData.zippyPlay = FALSE;
1031     }
1032
1033     /* [AS] Initialize pv info list [HGM] and game state */
1034     {
1035         int i, j;
1036
1037         for( i=0; i<=framePtr; i++ ) {
1038             pvInfoList[i].depth = -1;
1039             boards[i][EP_STATUS] = EP_NONE;
1040             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1041         }
1042     }
1043
1044     InitTimeControls();
1045
1046     /* [AS] Adjudication threshold */
1047     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1048
1049     InitEngine(&first, 0);
1050     InitEngine(&second, 1);
1051     CommonEngineInit();
1052
1053     pairing.which = "pairing"; // pairing engine
1054     pairing.pr = NoProc;
1055     pairing.isr = NULL;
1056     pairing.program = appData.pairingEngine;
1057     pairing.host = "localhost";
1058     pairing.dir = ".";
1059
1060     if (appData.icsActive) {
1061         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1062     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1063         appData.clockMode = FALSE;
1064         first.sendTime = second.sendTime = 0;
1065     }
1066
1067 #if ZIPPY
1068     /* Override some settings from environment variables, for backward
1069        compatibility.  Unfortunately it's not feasible to have the env
1070        vars just set defaults, at least in xboard.  Ugh.
1071     */
1072     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1073       ZippyInit();
1074     }
1075 #endif
1076
1077     if (!appData.icsActive) {
1078       char buf[MSG_SIZ];
1079       int len;
1080
1081       /* Check for variants that are supported only in ICS mode,
1082          or not at all.  Some that are accepted here nevertheless
1083          have bugs; see comments below.
1084       */
1085       VariantClass variant = StringToVariant(appData.variant);
1086       switch (variant) {
1087       case VariantBughouse:     /* need four players and two boards */
1088       case VariantKriegspiel:   /* need to hide pieces and move details */
1089         /* case VariantFischeRandom: (Fabien: moved below) */
1090         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), 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 VariantUnknown:
1098       case VariantLoadable:
1099       case Variant29:
1100       case Variant30:
1101       case Variant31:
1102       case Variant32:
1103       case Variant33:
1104       case Variant34:
1105       case Variant35:
1106       case Variant36:
1107       default:
1108         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1109         if( (len >= MSG_SIZ) && appData.debugMode )
1110           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1111
1112         DisplayFatalError(buf, 0, 2);
1113         return;
1114
1115       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1116       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1117       case VariantGothic:     /* [HGM] should work */
1118       case VariantCapablanca: /* [HGM] should work */
1119       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1120       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1121       case VariantKnightmate: /* [HGM] should work */
1122       case VariantCylinder:   /* [HGM] untested */
1123       case VariantFalcon:     /* [HGM] untested */
1124       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1125                                  offboard interposition not understood */
1126       case VariantNormal:     /* definitely works! */
1127       case VariantWildCastle: /* pieces not automatically shuffled */
1128       case VariantNoCastle:   /* pieces not automatically shuffled */
1129       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1130       case VariantLosers:     /* should work except for win condition,
1131                                  and doesn't know captures are mandatory */
1132       case VariantSuicide:    /* should work except for win condition,
1133                                  and doesn't know captures are mandatory */
1134       case VariantGiveaway:   /* should work except for win condition,
1135                                  and doesn't know captures are mandatory */
1136       case VariantTwoKings:   /* should work */
1137       case VariantAtomic:     /* should work except for win condition */
1138       case Variant3Check:     /* should work except for win condition */
1139       case VariantShatranj:   /* should work except for all win conditions */
1140       case VariantMakruk:     /* should work except for draw countdown */
1141       case VariantBerolina:   /* might work if TestLegality is off */
1142       case VariantCapaRandom: /* should work */
1143       case VariantJanus:      /* should work */
1144       case VariantSuper:      /* experimental */
1145       case VariantGreat:      /* experimental, requires legality testing to be off */
1146       case VariantSChess:     /* S-Chess, should work */
1147       case VariantGrand:      /* should work */
1148       case VariantSpartan:    /* should work */
1149         break;
1150       }
1151     }
1152
1153 }
1154
1155 int
1156 NextIntegerFromString (char ** str, long * value)
1157 {
1158     int result = -1;
1159     char * s = *str;
1160
1161     while( *s == ' ' || *s == '\t' ) {
1162         s++;
1163     }
1164
1165     *value = 0;
1166
1167     if( *s >= '0' && *s <= '9' ) {
1168         while( *s >= '0' && *s <= '9' ) {
1169             *value = *value * 10 + (*s - '0');
1170             s++;
1171         }
1172
1173         result = 0;
1174     }
1175
1176     *str = s;
1177
1178     return result;
1179 }
1180
1181 int
1182 NextTimeControlFromString (char ** str, long * value)
1183 {
1184     long temp;
1185     int result = NextIntegerFromString( str, &temp );
1186
1187     if( result == 0 ) {
1188         *value = temp * 60; /* Minutes */
1189         if( **str == ':' ) {
1190             (*str)++;
1191             result = NextIntegerFromString( str, &temp );
1192             *value += temp; /* Seconds */
1193         }
1194     }
1195
1196     return result;
1197 }
1198
1199 int
1200 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1201 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1202     int result = -1, type = 0; long temp, temp2;
1203
1204     if(**str != ':') return -1; // old params remain in force!
1205     (*str)++;
1206     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1207     if( NextIntegerFromString( str, &temp ) ) return -1;
1208     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1209
1210     if(**str != '/') {
1211         /* time only: incremental or sudden-death time control */
1212         if(**str == '+') { /* increment follows; read it */
1213             (*str)++;
1214             if(**str == '!') type = *(*str)++; // Bronstein TC
1215             if(result = NextIntegerFromString( str, &temp2)) return -1;
1216             *inc = temp2 * 1000;
1217             if(**str == '.') { // read fraction of increment
1218                 char *start = ++(*str);
1219                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1220                 temp2 *= 1000;
1221                 while(start++ < *str) temp2 /= 10;
1222                 *inc += temp2;
1223             }
1224         } else *inc = 0;
1225         *moves = 0; *tc = temp * 1000; *incType = type;
1226         return 0;
1227     }
1228
1229     (*str)++; /* classical time control */
1230     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1231
1232     if(result == 0) {
1233         *moves = temp;
1234         *tc    = temp2 * 1000;
1235         *inc   = 0;
1236         *incType = type;
1237     }
1238     return result;
1239 }
1240
1241 int
1242 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1243 {   /* [HGM] get time to add from the multi-session time-control string */
1244     int incType, moves=1; /* kludge to force reading of first session */
1245     long time, increment;
1246     char *s = tcString;
1247
1248     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1249     do {
1250         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1251         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1252         if(movenr == -1) return time;    /* last move before new session     */
1253         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1254         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1255         if(!moves) return increment;     /* current session is incremental   */
1256         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1257     } while(movenr >= -1);               /* try again for next session       */
1258
1259     return 0; // no new time quota on this move
1260 }
1261
1262 int
1263 ParseTimeControl (char *tc, float ti, int mps)
1264 {
1265   long tc1;
1266   long tc2;
1267   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1268   int min, sec=0;
1269
1270   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1271   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1272       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1273   if(ti > 0) {
1274
1275     if(mps)
1276       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1277     else 
1278       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1279   } else {
1280     if(mps)
1281       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1282     else 
1283       snprintf(buf, MSG_SIZ, ":%s", mytc);
1284   }
1285   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1286   
1287   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1288     return FALSE;
1289   }
1290
1291   if( *tc == '/' ) {
1292     /* Parse second time control */
1293     tc++;
1294
1295     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1296       return FALSE;
1297     }
1298
1299     if( tc2 == 0 ) {
1300       return FALSE;
1301     }
1302
1303     timeControl_2 = tc2 * 1000;
1304   }
1305   else {
1306     timeControl_2 = 0;
1307   }
1308
1309   if( tc1 == 0 ) {
1310     return FALSE;
1311   }
1312
1313   timeControl = tc1 * 1000;
1314
1315   if (ti >= 0) {
1316     timeIncrement = ti * 1000;  /* convert to ms */
1317     movesPerSession = 0;
1318   } else {
1319     timeIncrement = 0;
1320     movesPerSession = mps;
1321   }
1322   return TRUE;
1323 }
1324
1325 void
1326 InitBackEnd2 ()
1327 {
1328     if (appData.debugMode) {
1329         fprintf(debugFP, "%s\n", programVersion);
1330     }
1331     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1332
1333     set_cont_sequence(appData.wrapContSeq);
1334     if (appData.matchGames > 0) {
1335         appData.matchMode = TRUE;
1336     } else if (appData.matchMode) {
1337         appData.matchGames = 1;
1338     }
1339     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1340         appData.matchGames = appData.sameColorGames;
1341     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1342         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1343         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1344     }
1345     Reset(TRUE, FALSE);
1346     if (appData.noChessProgram || first.protocolVersion == 1) {
1347       InitBackEnd3();
1348     } else {
1349       /* kludge: allow timeout for initial "feature" commands */
1350       FreezeUI();
1351       DisplayMessage("", _("Starting chess program"));
1352       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1353     }
1354 }
1355
1356 int
1357 CalculateIndex (int index, int gameNr)
1358 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1359     int res;
1360     if(index > 0) return index; // fixed nmber
1361     if(index == 0) return 1;
1362     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1363     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1364     return res;
1365 }
1366
1367 int
1368 LoadGameOrPosition (int gameNr)
1369 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1370     if (*appData.loadGameFile != NULLCHAR) {
1371         if (!LoadGameFromFile(appData.loadGameFile,
1372                 CalculateIndex(appData.loadGameIndex, gameNr),
1373                               appData.loadGameFile, FALSE)) {
1374             DisplayFatalError(_("Bad game file"), 0, 1);
1375             return 0;
1376         }
1377     } else if (*appData.loadPositionFile != NULLCHAR) {
1378         if (!LoadPositionFromFile(appData.loadPositionFile,
1379                 CalculateIndex(appData.loadPositionIndex, gameNr),
1380                                   appData.loadPositionFile)) {
1381             DisplayFatalError(_("Bad position file"), 0, 1);
1382             return 0;
1383         }
1384     }
1385     return 1;
1386 }
1387
1388 void
1389 ReserveGame (int gameNr, char resChar)
1390 {
1391     FILE *tf = fopen(appData.tourneyFile, "r+");
1392     char *p, *q, c, buf[MSG_SIZ];
1393     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1394     safeStrCpy(buf, lastMsg, MSG_SIZ);
1395     DisplayMessage(_("Pick new game"), "");
1396     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1397     ParseArgsFromFile(tf);
1398     p = q = appData.results;
1399     if(appData.debugMode) {
1400       char *r = appData.participants;
1401       fprintf(debugFP, "results = '%s'\n", p);
1402       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1403       fprintf(debugFP, "\n");
1404     }
1405     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1406     nextGame = q - p;
1407     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1408     safeStrCpy(q, p, strlen(p) + 2);
1409     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1410     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1411     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1412         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1413         q[nextGame] = '*';
1414     }
1415     fseek(tf, -(strlen(p)+4), SEEK_END);
1416     c = fgetc(tf);
1417     if(c != '"') // depending on DOS or Unix line endings we can be one off
1418          fseek(tf, -(strlen(p)+2), SEEK_END);
1419     else fseek(tf, -(strlen(p)+3), SEEK_END);
1420     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1421     DisplayMessage(buf, "");
1422     free(p); appData.results = q;
1423     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1424        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1425       int round = appData.defaultMatchGames * appData.tourneyType;
1426       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1427          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1428         UnloadEngine(&first);  // next game belongs to other pairing;
1429         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1430     }
1431     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1432 }
1433
1434 void
1435 MatchEvent (int mode)
1436 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1437         int dummy;
1438         if(matchMode) { // already in match mode: switch it off
1439             abortMatch = TRUE;
1440             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1441             return;
1442         }
1443 //      if(gameMode != BeginningOfGame) {
1444 //          DisplayError(_("You can only start a match from the initial position."), 0);
1445 //          return;
1446 //      }
1447         abortMatch = FALSE;
1448         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1449         /* Set up machine vs. machine match */
1450         nextGame = 0;
1451         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1452         if(appData.tourneyFile[0]) {
1453             ReserveGame(-1, 0);
1454             if(nextGame > appData.matchGames) {
1455                 char buf[MSG_SIZ];
1456                 if(strchr(appData.results, '*') == NULL) {
1457                     FILE *f;
1458                     appData.tourneyCycles++;
1459                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1460                         fclose(f);
1461                         NextTourneyGame(-1, &dummy);
1462                         ReserveGame(-1, 0);
1463                         if(nextGame <= appData.matchGames) {
1464                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1465                             matchMode = mode;
1466                             ScheduleDelayedEvent(NextMatchGame, 10000);
1467                             return;
1468                         }
1469                     }
1470                 }
1471                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1472                 DisplayError(buf, 0);
1473                 appData.tourneyFile[0] = 0;
1474                 return;
1475             }
1476         } else
1477         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1478             DisplayFatalError(_("Can't have a match with no chess programs"),
1479                               0, 2);
1480             return;
1481         }
1482         matchMode = mode;
1483         matchGame = roundNr = 1;
1484         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1485         NextMatchGame();
1486 }
1487
1488 void
1489 InitBackEnd3 P((void))
1490 {
1491     GameMode initialMode;
1492     char buf[MSG_SIZ];
1493     int err, len;
1494
1495     InitChessProgram(&first, startedFromSetupPosition);
1496
1497     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1498         free(programVersion);
1499         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1500         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1501         FloatToFront(&appData.recentEngineList, appData.firstChessProgram);
1502     }
1503
1504     if (appData.icsActive) {
1505 #ifdef WIN32
1506         /* [DM] Make a console window if needed [HGM] merged ifs */
1507         ConsoleCreate();
1508 #endif
1509         err = establish();
1510         if (err != 0)
1511           {
1512             if (*appData.icsCommPort != NULLCHAR)
1513               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1514                              appData.icsCommPort);
1515             else
1516               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1517                         appData.icsHost, appData.icsPort);
1518
1519             if( (len >= MSG_SIZ) && appData.debugMode )
1520               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1521
1522             DisplayFatalError(buf, err, 1);
1523             return;
1524         }
1525         SetICSMode();
1526         telnetISR =
1527           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1528         fromUserISR =
1529           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1530         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1531             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1532     } else if (appData.noChessProgram) {
1533         SetNCPMode();
1534     } else {
1535         SetGNUMode();
1536     }
1537
1538     if (*appData.cmailGameName != NULLCHAR) {
1539         SetCmailMode();
1540         OpenLoopback(&cmailPR);
1541         cmailISR =
1542           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1543     }
1544
1545     ThawUI();
1546     DisplayMessage("", "");
1547     if (StrCaseCmp(appData.initialMode, "") == 0) {
1548       initialMode = BeginningOfGame;
1549       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1550         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1551         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1552         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1553         ModeHighlight();
1554       }
1555     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1556       initialMode = TwoMachinesPlay;
1557     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1558       initialMode = AnalyzeFile;
1559     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1560       initialMode = AnalyzeMode;
1561     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1562       initialMode = MachinePlaysWhite;
1563     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1564       initialMode = MachinePlaysBlack;
1565     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1566       initialMode = EditGame;
1567     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1568       initialMode = EditPosition;
1569     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1570       initialMode = Training;
1571     } else {
1572       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1573       if( (len >= MSG_SIZ) && appData.debugMode )
1574         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1575
1576       DisplayFatalError(buf, 0, 2);
1577       return;
1578     }
1579
1580     if (appData.matchMode) {
1581         if(appData.tourneyFile[0]) { // start tourney from command line
1582             FILE *f;
1583             if(f = fopen(appData.tourneyFile, "r")) {
1584                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1585                 fclose(f);
1586                 appData.clockMode = TRUE;
1587                 SetGNUMode();
1588             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1589         }
1590         MatchEvent(TRUE);
1591     } else if (*appData.cmailGameName != NULLCHAR) {
1592         /* Set up cmail mode */
1593         ReloadCmailMsgEvent(TRUE);
1594     } else {
1595         /* Set up other modes */
1596         if (initialMode == AnalyzeFile) {
1597           if (*appData.loadGameFile == NULLCHAR) {
1598             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1599             return;
1600           }
1601         }
1602         if (*appData.loadGameFile != NULLCHAR) {
1603             (void) LoadGameFromFile(appData.loadGameFile,
1604                                     appData.loadGameIndex,
1605                                     appData.loadGameFile, TRUE);
1606         } else if (*appData.loadPositionFile != NULLCHAR) {
1607             (void) LoadPositionFromFile(appData.loadPositionFile,
1608                                         appData.loadPositionIndex,
1609                                         appData.loadPositionFile);
1610             /* [HGM] try to make self-starting even after FEN load */
1611             /* to allow automatic setup of fairy variants with wtm */
1612             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1613                 gameMode = BeginningOfGame;
1614                 setboardSpoiledMachineBlack = 1;
1615             }
1616             /* [HGM] loadPos: make that every new game uses the setup */
1617             /* from file as long as we do not switch variant          */
1618             if(!blackPlaysFirst) {
1619                 startedFromPositionFile = TRUE;
1620                 CopyBoard(filePosition, boards[0]);
1621             }
1622         }
1623         if (initialMode == AnalyzeMode) {
1624           if (appData.noChessProgram) {
1625             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1626             return;
1627           }
1628           if (appData.icsActive) {
1629             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1630             return;
1631           }
1632           AnalyzeModeEvent();
1633         } else if (initialMode == AnalyzeFile) {
1634           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1635           ShowThinkingEvent();
1636           AnalyzeFileEvent();
1637           AnalysisPeriodicEvent(1);
1638         } else if (initialMode == MachinePlaysWhite) {
1639           if (appData.noChessProgram) {
1640             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1641                               0, 2);
1642             return;
1643           }
1644           if (appData.icsActive) {
1645             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1646                               0, 2);
1647             return;
1648           }
1649           MachineWhiteEvent();
1650         } else if (initialMode == MachinePlaysBlack) {
1651           if (appData.noChessProgram) {
1652             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1653                               0, 2);
1654             return;
1655           }
1656           if (appData.icsActive) {
1657             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1658                               0, 2);
1659             return;
1660           }
1661           MachineBlackEvent();
1662         } else if (initialMode == TwoMachinesPlay) {
1663           if (appData.noChessProgram) {
1664             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1665                               0, 2);
1666             return;
1667           }
1668           if (appData.icsActive) {
1669             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1670                               0, 2);
1671             return;
1672           }
1673           TwoMachinesEvent();
1674         } else if (initialMode == EditGame) {
1675           EditGameEvent();
1676         } else if (initialMode == EditPosition) {
1677           EditPositionEvent();
1678         } else if (initialMode == Training) {
1679           if (*appData.loadGameFile == NULLCHAR) {
1680             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1681             return;
1682           }
1683           TrainingEvent();
1684         }
1685     }
1686 }
1687
1688 void
1689 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1690 {
1691     DisplayBook(current+1);
1692
1693     MoveHistorySet( movelist, first, last, current, pvInfoList );
1694
1695     EvalGraphSet( first, last, current, pvInfoList );
1696
1697     MakeEngineOutputTitle();
1698 }
1699
1700 /*
1701  * Establish will establish a contact to a remote host.port.
1702  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1703  *  used to talk to the host.
1704  * Returns 0 if okay, error code if not.
1705  */
1706 int
1707 establish ()
1708 {
1709     char buf[MSG_SIZ];
1710
1711     if (*appData.icsCommPort != NULLCHAR) {
1712         /* Talk to the host through a serial comm port */
1713         return OpenCommPort(appData.icsCommPort, &icsPR);
1714
1715     } else if (*appData.gateway != NULLCHAR) {
1716         if (*appData.remoteShell == NULLCHAR) {
1717             /* Use the rcmd protocol to run telnet program on a gateway host */
1718             snprintf(buf, sizeof(buf), "%s %s %s",
1719                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1720             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1721
1722         } else {
1723             /* Use the rsh program to run telnet program on a gateway host */
1724             if (*appData.remoteUser == NULLCHAR) {
1725                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1726                         appData.gateway, appData.telnetProgram,
1727                         appData.icsHost, appData.icsPort);
1728             } else {
1729                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1730                         appData.remoteShell, appData.gateway,
1731                         appData.remoteUser, appData.telnetProgram,
1732                         appData.icsHost, appData.icsPort);
1733             }
1734             return StartChildProcess(buf, "", &icsPR);
1735
1736         }
1737     } else if (appData.useTelnet) {
1738         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1739
1740     } else {
1741         /* TCP socket interface differs somewhat between
1742            Unix and NT; handle details in the front end.
1743            */
1744         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1745     }
1746 }
1747
1748 void
1749 EscapeExpand (char *p, char *q)
1750 {       // [HGM] initstring: routine to shape up string arguments
1751         while(*p++ = *q++) if(p[-1] == '\\')
1752             switch(*q++) {
1753                 case 'n': p[-1] = '\n'; break;
1754                 case 'r': p[-1] = '\r'; break;
1755                 case 't': p[-1] = '\t'; break;
1756                 case '\\': p[-1] = '\\'; break;
1757                 case 0: *p = 0; return;
1758                 default: p[-1] = q[-1]; break;
1759             }
1760 }
1761
1762 void
1763 show_bytes (FILE *fp, char *buf, int count)
1764 {
1765     while (count--) {
1766         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1767             fprintf(fp, "\\%03o", *buf & 0xff);
1768         } else {
1769             putc(*buf, fp);
1770         }
1771         buf++;
1772     }
1773     fflush(fp);
1774 }
1775
1776 /* Returns an errno value */
1777 int
1778 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1779 {
1780     char buf[8192], *p, *q, *buflim;
1781     int left, newcount, outcount;
1782
1783     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1784         *appData.gateway != NULLCHAR) {
1785         if (appData.debugMode) {
1786             fprintf(debugFP, ">ICS: ");
1787             show_bytes(debugFP, message, count);
1788             fprintf(debugFP, "\n");
1789         }
1790         return OutputToProcess(pr, message, count, outError);
1791     }
1792
1793     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1794     p = message;
1795     q = buf;
1796     left = count;
1797     newcount = 0;
1798     while (left) {
1799         if (q >= buflim) {
1800             if (appData.debugMode) {
1801                 fprintf(debugFP, ">ICS: ");
1802                 show_bytes(debugFP, buf, newcount);
1803                 fprintf(debugFP, "\n");
1804             }
1805             outcount = OutputToProcess(pr, buf, newcount, outError);
1806             if (outcount < newcount) return -1; /* to be sure */
1807             q = buf;
1808             newcount = 0;
1809         }
1810         if (*p == '\n') {
1811             *q++ = '\r';
1812             newcount++;
1813         } else if (((unsigned char) *p) == TN_IAC) {
1814             *q++ = (char) TN_IAC;
1815             newcount ++;
1816         }
1817         *q++ = *p++;
1818         newcount++;
1819         left--;
1820     }
1821     if (appData.debugMode) {
1822         fprintf(debugFP, ">ICS: ");
1823         show_bytes(debugFP, buf, newcount);
1824         fprintf(debugFP, "\n");
1825     }
1826     outcount = OutputToProcess(pr, buf, newcount, outError);
1827     if (outcount < newcount) return -1; /* to be sure */
1828     return count;
1829 }
1830
1831 void
1832 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1833 {
1834     int outError, outCount;
1835     static int gotEof = 0;
1836
1837     /* Pass data read from player on to ICS */
1838     if (count > 0) {
1839         gotEof = 0;
1840         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1841         if (outCount < count) {
1842             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1843         }
1844     } else if (count < 0) {
1845         RemoveInputSource(isr);
1846         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1847     } else if (gotEof++ > 0) {
1848         RemoveInputSource(isr);
1849         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1850     }
1851 }
1852
1853 void
1854 KeepAlive ()
1855 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1856     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1857     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1858     SendToICS("date\n");
1859     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1860 }
1861
1862 /* added routine for printf style output to ics */
1863 void
1864 ics_printf (char *format, ...)
1865 {
1866     char buffer[MSG_SIZ];
1867     va_list args;
1868
1869     va_start(args, format);
1870     vsnprintf(buffer, sizeof(buffer), format, args);
1871     buffer[sizeof(buffer)-1] = '\0';
1872     SendToICS(buffer);
1873     va_end(args);
1874 }
1875
1876 void
1877 SendToICS (char *s)
1878 {
1879     int count, outCount, outError;
1880
1881     if (icsPR == NoProc) return;
1882
1883     count = strlen(s);
1884     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1885     if (outCount < count) {
1886         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1887     }
1888 }
1889
1890 /* This is used for sending logon scripts to the ICS. Sending
1891    without a delay causes problems when using timestamp on ICC
1892    (at least on my machine). */
1893 void
1894 SendToICSDelayed (char *s, long msdelay)
1895 {
1896     int count, outCount, outError;
1897
1898     if (icsPR == NoProc) return;
1899
1900     count = strlen(s);
1901     if (appData.debugMode) {
1902         fprintf(debugFP, ">ICS: ");
1903         show_bytes(debugFP, s, count);
1904         fprintf(debugFP, "\n");
1905     }
1906     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1907                                       msdelay);
1908     if (outCount < count) {
1909         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1910     }
1911 }
1912
1913
1914 /* Remove all highlighting escape sequences in s
1915    Also deletes any suffix starting with '('
1916    */
1917 char *
1918 StripHighlightAndTitle (char *s)
1919 {
1920     static char retbuf[MSG_SIZ];
1921     char *p = retbuf;
1922
1923     while (*s != NULLCHAR) {
1924         while (*s == '\033') {
1925             while (*s != NULLCHAR && !isalpha(*s)) s++;
1926             if (*s != NULLCHAR) s++;
1927         }
1928         while (*s != NULLCHAR && *s != '\033') {
1929             if (*s == '(' || *s == '[') {
1930                 *p = NULLCHAR;
1931                 return retbuf;
1932             }
1933             *p++ = *s++;
1934         }
1935     }
1936     *p = NULLCHAR;
1937     return retbuf;
1938 }
1939
1940 /* Remove all highlighting escape sequences in s */
1941 char *
1942 StripHighlight (char *s)
1943 {
1944     static char retbuf[MSG_SIZ];
1945     char *p = retbuf;
1946
1947     while (*s != NULLCHAR) {
1948         while (*s == '\033') {
1949             while (*s != NULLCHAR && !isalpha(*s)) s++;
1950             if (*s != NULLCHAR) s++;
1951         }
1952         while (*s != NULLCHAR && *s != '\033') {
1953             *p++ = *s++;
1954         }
1955     }
1956     *p = NULLCHAR;
1957     return retbuf;
1958 }
1959
1960 char *variantNames[] = VARIANT_NAMES;
1961 char *
1962 VariantName (VariantClass v)
1963 {
1964     return variantNames[v];
1965 }
1966
1967
1968 /* Identify a variant from the strings the chess servers use or the
1969    PGN Variant tag names we use. */
1970 VariantClass
1971 StringToVariant (char *e)
1972 {
1973     char *p;
1974     int wnum = -1;
1975     VariantClass v = VariantNormal;
1976     int i, found = FALSE;
1977     char buf[MSG_SIZ];
1978     int len;
1979
1980     if (!e) return v;
1981
1982     /* [HGM] skip over optional board-size prefixes */
1983     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1984         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1985         while( *e++ != '_');
1986     }
1987
1988     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1989         v = VariantNormal;
1990         found = TRUE;
1991     } else
1992     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1993       if (StrCaseStr(e, variantNames[i])) {
1994         v = (VariantClass) i;
1995         found = TRUE;
1996         break;
1997       }
1998     }
1999
2000     if (!found) {
2001       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2002           || StrCaseStr(e, "wild/fr")
2003           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2004         v = VariantFischeRandom;
2005       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2006                  (i = 1, p = StrCaseStr(e, "w"))) {
2007         p += i;
2008         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2009         if (isdigit(*p)) {
2010           wnum = atoi(p);
2011         } else {
2012           wnum = -1;
2013         }
2014         switch (wnum) {
2015         case 0: /* FICS only, actually */
2016         case 1:
2017           /* Castling legal even if K starts on d-file */
2018           v = VariantWildCastle;
2019           break;
2020         case 2:
2021         case 3:
2022         case 4:
2023           /* Castling illegal even if K & R happen to start in
2024              normal positions. */
2025           v = VariantNoCastle;
2026           break;
2027         case 5:
2028         case 7:
2029         case 8:
2030         case 10:
2031         case 11:
2032         case 12:
2033         case 13:
2034         case 14:
2035         case 15:
2036         case 18:
2037         case 19:
2038           /* Castling legal iff K & R start in normal positions */
2039           v = VariantNormal;
2040           break;
2041         case 6:
2042         case 20:
2043         case 21:
2044           /* Special wilds for position setup; unclear what to do here */
2045           v = VariantLoadable;
2046           break;
2047         case 9:
2048           /* Bizarre ICC game */
2049           v = VariantTwoKings;
2050           break;
2051         case 16:
2052           v = VariantKriegspiel;
2053           break;
2054         case 17:
2055           v = VariantLosers;
2056           break;
2057         case 22:
2058           v = VariantFischeRandom;
2059           break;
2060         case 23:
2061           v = VariantCrazyhouse;
2062           break;
2063         case 24:
2064           v = VariantBughouse;
2065           break;
2066         case 25:
2067           v = Variant3Check;
2068           break;
2069         case 26:
2070           /* Not quite the same as FICS suicide! */
2071           v = VariantGiveaway;
2072           break;
2073         case 27:
2074           v = VariantAtomic;
2075           break;
2076         case 28:
2077           v = VariantShatranj;
2078           break;
2079
2080         /* Temporary names for future ICC types.  The name *will* change in
2081            the next xboard/WinBoard release after ICC defines it. */
2082         case 29:
2083           v = Variant29;
2084           break;
2085         case 30:
2086           v = Variant30;
2087           break;
2088         case 31:
2089           v = Variant31;
2090           break;
2091         case 32:
2092           v = Variant32;
2093           break;
2094         case 33:
2095           v = Variant33;
2096           break;
2097         case 34:
2098           v = Variant34;
2099           break;
2100         case 35:
2101           v = Variant35;
2102           break;
2103         case 36:
2104           v = Variant36;
2105           break;
2106         case 37:
2107           v = VariantShogi;
2108           break;
2109         case 38:
2110           v = VariantXiangqi;
2111           break;
2112         case 39:
2113           v = VariantCourier;
2114           break;
2115         case 40:
2116           v = VariantGothic;
2117           break;
2118         case 41:
2119           v = VariantCapablanca;
2120           break;
2121         case 42:
2122           v = VariantKnightmate;
2123           break;
2124         case 43:
2125           v = VariantFairy;
2126           break;
2127         case 44:
2128           v = VariantCylinder;
2129           break;
2130         case 45:
2131           v = VariantFalcon;
2132           break;
2133         case 46:
2134           v = VariantCapaRandom;
2135           break;
2136         case 47:
2137           v = VariantBerolina;
2138           break;
2139         case 48:
2140           v = VariantJanus;
2141           break;
2142         case 49:
2143           v = VariantSuper;
2144           break;
2145         case 50:
2146           v = VariantGreat;
2147           break;
2148         case -1:
2149           /* Found "wild" or "w" in the string but no number;
2150              must assume it's normal chess. */
2151           v = VariantNormal;
2152           break;
2153         default:
2154           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2155           if( (len >= MSG_SIZ) && appData.debugMode )
2156             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2157
2158           DisplayError(buf, 0);
2159           v = VariantUnknown;
2160           break;
2161         }
2162       }
2163     }
2164     if (appData.debugMode) {
2165       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2166               e, wnum, VariantName(v));
2167     }
2168     return v;
2169 }
2170
2171 static int leftover_start = 0, leftover_len = 0;
2172 char star_match[STAR_MATCH_N][MSG_SIZ];
2173
2174 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2175    advance *index beyond it, and set leftover_start to the new value of
2176    *index; else return FALSE.  If pattern contains the character '*', it
2177    matches any sequence of characters not containing '\r', '\n', or the
2178    character following the '*' (if any), and the matched sequence(s) are
2179    copied into star_match.
2180    */
2181 int
2182 looking_at ( char *buf, int *index, char *pattern)
2183 {
2184     char *bufp = &buf[*index], *patternp = pattern;
2185     int star_count = 0;
2186     char *matchp = star_match[0];
2187
2188     for (;;) {
2189         if (*patternp == NULLCHAR) {
2190             *index = leftover_start = bufp - buf;
2191             *matchp = NULLCHAR;
2192             return TRUE;
2193         }
2194         if (*bufp == NULLCHAR) return FALSE;
2195         if (*patternp == '*') {
2196             if (*bufp == *(patternp + 1)) {
2197                 *matchp = NULLCHAR;
2198                 matchp = star_match[++star_count];
2199                 patternp += 2;
2200                 bufp++;
2201                 continue;
2202             } else if (*bufp == '\n' || *bufp == '\r') {
2203                 patternp++;
2204                 if (*patternp == NULLCHAR)
2205                   continue;
2206                 else
2207                   return FALSE;
2208             } else {
2209                 *matchp++ = *bufp++;
2210                 continue;
2211             }
2212         }
2213         if (*patternp != *bufp) return FALSE;
2214         patternp++;
2215         bufp++;
2216     }
2217 }
2218
2219 void
2220 SendToPlayer (char *data, int length)
2221 {
2222     int error, outCount;
2223     outCount = OutputToProcess(NoProc, data, length, &error);
2224     if (outCount < length) {
2225         DisplayFatalError(_("Error writing to display"), error, 1);
2226     }
2227 }
2228
2229 void
2230 PackHolding (char packed[], char *holding)
2231 {
2232     char *p = holding;
2233     char *q = packed;
2234     int runlength = 0;
2235     int curr = 9999;
2236     do {
2237         if (*p == curr) {
2238             runlength++;
2239         } else {
2240             switch (runlength) {
2241               case 0:
2242                 break;
2243               case 1:
2244                 *q++ = curr;
2245                 break;
2246               case 2:
2247                 *q++ = curr;
2248                 *q++ = curr;
2249                 break;
2250               default:
2251                 sprintf(q, "%d", runlength);
2252                 while (*q) q++;
2253                 *q++ = curr;
2254                 break;
2255             }
2256             runlength = 1;
2257             curr = *p;
2258         }
2259     } while (*p++);
2260     *q = NULLCHAR;
2261 }
2262
2263 /* Telnet protocol requests from the front end */
2264 void
2265 TelnetRequest (unsigned char ddww, unsigned char option)
2266 {
2267     unsigned char msg[3];
2268     int outCount, outError;
2269
2270     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2271
2272     if (appData.debugMode) {
2273         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2274         switch (ddww) {
2275           case TN_DO:
2276             ddwwStr = "DO";
2277             break;
2278           case TN_DONT:
2279             ddwwStr = "DONT";
2280             break;
2281           case TN_WILL:
2282             ddwwStr = "WILL";
2283             break;
2284           case TN_WONT:
2285             ddwwStr = "WONT";
2286             break;
2287           default:
2288             ddwwStr = buf1;
2289             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2290             break;
2291         }
2292         switch (option) {
2293           case TN_ECHO:
2294             optionStr = "ECHO";
2295             break;
2296           default:
2297             optionStr = buf2;
2298             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2299             break;
2300         }
2301         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2302     }
2303     msg[0] = TN_IAC;
2304     msg[1] = ddww;
2305     msg[2] = option;
2306     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2307     if (outCount < 3) {
2308         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2309     }
2310 }
2311
2312 void
2313 DoEcho ()
2314 {
2315     if (!appData.icsActive) return;
2316     TelnetRequest(TN_DO, TN_ECHO);
2317 }
2318
2319 void
2320 DontEcho ()
2321 {
2322     if (!appData.icsActive) return;
2323     TelnetRequest(TN_DONT, TN_ECHO);
2324 }
2325
2326 void
2327 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2328 {
2329     /* put the holdings sent to us by the server on the board holdings area */
2330     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2331     char p;
2332     ChessSquare piece;
2333
2334     if(gameInfo.holdingsWidth < 2)  return;
2335     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2336         return; // prevent overwriting by pre-board holdings
2337
2338     if( (int)lowestPiece >= BlackPawn ) {
2339         holdingsColumn = 0;
2340         countsColumn = 1;
2341         holdingsStartRow = BOARD_HEIGHT-1;
2342         direction = -1;
2343     } else {
2344         holdingsColumn = BOARD_WIDTH-1;
2345         countsColumn = BOARD_WIDTH-2;
2346         holdingsStartRow = 0;
2347         direction = 1;
2348     }
2349
2350     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2351         board[i][holdingsColumn] = EmptySquare;
2352         board[i][countsColumn]   = (ChessSquare) 0;
2353     }
2354     while( (p=*holdings++) != NULLCHAR ) {
2355         piece = CharToPiece( ToUpper(p) );
2356         if(piece == EmptySquare) continue;
2357         /*j = (int) piece - (int) WhitePawn;*/
2358         j = PieceToNumber(piece);
2359         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2360         if(j < 0) continue;               /* should not happen */
2361         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2362         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2363         board[holdingsStartRow+j*direction][countsColumn]++;
2364     }
2365 }
2366
2367
2368 void
2369 VariantSwitch (Board board, VariantClass newVariant)
2370 {
2371    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2372    static Board oldBoard;
2373
2374    startedFromPositionFile = FALSE;
2375    if(gameInfo.variant == newVariant) return;
2376
2377    /* [HGM] This routine is called each time an assignment is made to
2378     * gameInfo.variant during a game, to make sure the board sizes
2379     * are set to match the new variant. If that means adding or deleting
2380     * holdings, we shift the playing board accordingly
2381     * This kludge is needed because in ICS observe mode, we get boards
2382     * of an ongoing game without knowing the variant, and learn about the
2383     * latter only later. This can be because of the move list we requested,
2384     * in which case the game history is refilled from the beginning anyway,
2385     * but also when receiving holdings of a crazyhouse game. In the latter
2386     * case we want to add those holdings to the already received position.
2387     */
2388
2389
2390    if (appData.debugMode) {
2391      fprintf(debugFP, "Switch board from %s to %s\n",
2392              VariantName(gameInfo.variant), VariantName(newVariant));
2393      setbuf(debugFP, NULL);
2394    }
2395    shuffleOpenings = 0;       /* [HGM] shuffle */
2396    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2397    switch(newVariant)
2398      {
2399      case VariantShogi:
2400        newWidth = 9;  newHeight = 9;
2401        gameInfo.holdingsSize = 7;
2402      case VariantBughouse:
2403      case VariantCrazyhouse:
2404        newHoldingsWidth = 2; break;
2405      case VariantGreat:
2406        newWidth = 10;
2407      case VariantSuper:
2408        newHoldingsWidth = 2;
2409        gameInfo.holdingsSize = 8;
2410        break;
2411      case VariantGothic:
2412      case VariantCapablanca:
2413      case VariantCapaRandom:
2414        newWidth = 10;
2415      default:
2416        newHoldingsWidth = gameInfo.holdingsSize = 0;
2417      };
2418
2419    if(newWidth  != gameInfo.boardWidth  ||
2420       newHeight != gameInfo.boardHeight ||
2421       newHoldingsWidth != gameInfo.holdingsWidth ) {
2422
2423      /* shift position to new playing area, if needed */
2424      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2425        for(i=0; i<BOARD_HEIGHT; i++)
2426          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2427            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2428              board[i][j];
2429        for(i=0; i<newHeight; i++) {
2430          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2431          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2432        }
2433      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2434        for(i=0; i<BOARD_HEIGHT; i++)
2435          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2436            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2437              board[i][j];
2438      }
2439      gameInfo.boardWidth  = newWidth;
2440      gameInfo.boardHeight = newHeight;
2441      gameInfo.holdingsWidth = newHoldingsWidth;
2442      gameInfo.variant = newVariant;
2443      InitDrawingSizes(-2, 0);
2444    } else gameInfo.variant = newVariant;
2445    CopyBoard(oldBoard, board);   // remember correctly formatted board
2446      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2447    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2448 }
2449
2450 static int loggedOn = FALSE;
2451
2452 /*-- Game start info cache: --*/
2453 int gs_gamenum;
2454 char gs_kind[MSG_SIZ];
2455 static char player1Name[128] = "";
2456 static char player2Name[128] = "";
2457 static char cont_seq[] = "\n\\   ";
2458 static int player1Rating = -1;
2459 static int player2Rating = -1;
2460 /*----------------------------*/
2461
2462 ColorClass curColor = ColorNormal;
2463 int suppressKibitz = 0;
2464
2465 // [HGM] seekgraph
2466 Boolean soughtPending = FALSE;
2467 Boolean seekGraphUp;
2468 #define MAX_SEEK_ADS 200
2469 #define SQUARE 0x80
2470 char *seekAdList[MAX_SEEK_ADS];
2471 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2472 float tcList[MAX_SEEK_ADS];
2473 char colorList[MAX_SEEK_ADS];
2474 int nrOfSeekAds = 0;
2475 int minRating = 1010, maxRating = 2800;
2476 int hMargin = 10, vMargin = 20, h, w;
2477 extern int squareSize, lineGap;
2478
2479 void
2480 PlotSeekAd (int i)
2481 {
2482         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2483         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2484         if(r < minRating+100 && r >=0 ) r = minRating+100;
2485         if(r > maxRating) r = maxRating;
2486         if(tc < 1.) tc = 1.;
2487         if(tc > 95.) tc = 95.;
2488         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2489         y = ((double)r - minRating)/(maxRating - minRating)
2490             * (h-vMargin-squareSize/8-1) + vMargin;
2491         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2492         if(strstr(seekAdList[i], " u ")) color = 1;
2493         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2494            !strstr(seekAdList[i], "bullet") &&
2495            !strstr(seekAdList[i], "blitz") &&
2496            !strstr(seekAdList[i], "standard") ) color = 2;
2497         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2498         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2499 }
2500
2501 void
2502 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2503 {
2504         char buf[MSG_SIZ], *ext = "";
2505         VariantClass v = StringToVariant(type);
2506         if(strstr(type, "wild")) {
2507             ext = type + 4; // append wild number
2508             if(v == VariantFischeRandom) type = "chess960"; else
2509             if(v == VariantLoadable) type = "setup"; else
2510             type = VariantName(v);
2511         }
2512         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2513         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2514             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2515             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2516             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2517             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2518             seekNrList[nrOfSeekAds] = nr;
2519             zList[nrOfSeekAds] = 0;
2520             seekAdList[nrOfSeekAds++] = StrSave(buf);
2521             if(plot) PlotSeekAd(nrOfSeekAds-1);
2522         }
2523 }
2524
2525 void
2526 EraseSeekDot (int i)
2527 {
2528     int x = xList[i], y = yList[i], d=squareSize/4, k;
2529     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2530     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2531     // now replot every dot that overlapped
2532     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2533         int xx = xList[k], yy = yList[k];
2534         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2535             DrawSeekDot(xx, yy, colorList[k]);
2536     }
2537 }
2538
2539 void
2540 RemoveSeekAd (int nr)
2541 {
2542         int i;
2543         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2544             EraseSeekDot(i);
2545             if(seekAdList[i]) free(seekAdList[i]);
2546             seekAdList[i] = seekAdList[--nrOfSeekAds];
2547             seekNrList[i] = seekNrList[nrOfSeekAds];
2548             ratingList[i] = ratingList[nrOfSeekAds];
2549             colorList[i]  = colorList[nrOfSeekAds];
2550             tcList[i] = tcList[nrOfSeekAds];
2551             xList[i]  = xList[nrOfSeekAds];
2552             yList[i]  = yList[nrOfSeekAds];
2553             zList[i]  = zList[nrOfSeekAds];
2554             seekAdList[nrOfSeekAds] = NULL;
2555             break;
2556         }
2557 }
2558
2559 Boolean
2560 MatchSoughtLine (char *line)
2561 {
2562     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2563     int nr, base, inc, u=0; char dummy;
2564
2565     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2566        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2567        (u=1) &&
2568        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2569         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2570         // match: compact and save the line
2571         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2572         return TRUE;
2573     }
2574     return FALSE;
2575 }
2576
2577 int
2578 DrawSeekGraph ()
2579 {
2580     int i;
2581     if(!seekGraphUp) return FALSE;
2582     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2583     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2584
2585     DrawSeekBackground(0, 0, w, h);
2586     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2587     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2588     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2589         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2590         yy = h-1-yy;
2591         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2592         if(i%500 == 0) {
2593             char buf[MSG_SIZ];
2594             snprintf(buf, MSG_SIZ, "%d", i);
2595             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2596         }
2597     }
2598     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2599     for(i=1; i<100; i+=(i<10?1:5)) {
2600         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2601         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2602         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2603             char buf[MSG_SIZ];
2604             snprintf(buf, MSG_SIZ, "%d", i);
2605             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2606         }
2607     }
2608     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2609     return TRUE;
2610 }
2611
2612 int
2613 SeekGraphClick (ClickType click, int x, int y, int moving)
2614 {
2615     static int lastDown = 0, displayed = 0, lastSecond;
2616     if(y < 0) return FALSE;
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 (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2667 {
2668 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2669 #define STARTED_NONE 0
2670 #define STARTED_MOVES 1
2671 #define STARTED_BOARD 2
2672 #define STARTED_OBSERVE 3
2673 #define STARTED_HOLDINGS 4
2674 #define STARTED_CHATTER 5
2675 #define STARTED_COMMENT 6
2676 #define STARTED_MOVES_NOHIDE 7
2677
2678     static int started = STARTED_NONE;
2679     static char parse[20000];
2680     static int parse_pos = 0;
2681     static char buf[BUF_SIZE + 1];
2682     static int firstTime = TRUE, intfSet = FALSE;
2683     static ColorClass prevColor = ColorNormal;
2684     static int savingComment = FALSE;
2685     static int cmatch = 0; // continuation sequence match
2686     char *bp;
2687     char str[MSG_SIZ];
2688     int i, oldi;
2689     int buf_len;
2690     int next_out;
2691     int tkind;
2692     int backup;    /* [DM] For zippy color lines */
2693     char *p;
2694     char talker[MSG_SIZ]; // [HGM] chat
2695     int channel;
2696
2697     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2698
2699     if (appData.debugMode) {
2700       if (!error) {
2701         fprintf(debugFP, "<ICS: ");
2702         show_bytes(debugFP, data, count);
2703         fprintf(debugFP, "\n");
2704       }
2705     }
2706
2707     if (appData.debugMode) { int f = forwardMostMove;
2708         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2709                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2710                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2711     }
2712     if (count > 0) {
2713         /* If last read ended with a partial line that we couldn't parse,
2714            prepend it to the new read and try again. */
2715         if (leftover_len > 0) {
2716             for (i=0; i<leftover_len; i++)
2717               buf[i] = buf[leftover_start + i];
2718         }
2719
2720     /* copy new characters into the buffer */
2721     bp = buf + leftover_len;
2722     buf_len=leftover_len;
2723     for (i=0; i<count; i++)
2724     {
2725         // ignore these
2726         if (data[i] == '\r')
2727             continue;
2728
2729         // join lines split by ICS?
2730         if (!appData.noJoin)
2731         {
2732             /*
2733                 Joining just consists of finding matches against the
2734                 continuation sequence, and discarding that sequence
2735                 if found instead of copying it.  So, until a match
2736                 fails, there's nothing to do since it might be the
2737                 complete sequence, and thus, something we don't want
2738                 copied.
2739             */
2740             if (data[i] == cont_seq[cmatch])
2741             {
2742                 cmatch++;
2743                 if (cmatch == strlen(cont_seq))
2744                 {
2745                     cmatch = 0; // complete match.  just reset the counter
2746
2747                     /*
2748                         it's possible for the ICS to not include the space
2749                         at the end of the last word, making our [correct]
2750                         join operation fuse two separate words.  the server
2751                         does this when the space occurs at the width setting.
2752                     */
2753                     if (!buf_len || buf[buf_len-1] != ' ')
2754                     {
2755                         *bp++ = ' ';
2756                         buf_len++;
2757                     }
2758                 }
2759                 continue;
2760             }
2761             else if (cmatch)
2762             {
2763                 /*
2764                     match failed, so we have to copy what matched before
2765                     falling through and copying this character.  In reality,
2766                     this will only ever be just the newline character, but
2767                     it doesn't hurt to be precise.
2768                 */
2769                 strncpy(bp, cont_seq, cmatch);
2770                 bp += cmatch;
2771                 buf_len += cmatch;
2772                 cmatch = 0;
2773             }
2774         }
2775
2776         // copy this char
2777         *bp++ = data[i];
2778         buf_len++;
2779     }
2780
2781         buf[buf_len] = NULLCHAR;
2782 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2783         next_out = 0;
2784         leftover_start = 0;
2785
2786         i = 0;
2787         while (i < buf_len) {
2788             /* Deal with part of the TELNET option negotiation
2789                protocol.  We refuse to do anything beyond the
2790                defaults, except that we allow the WILL ECHO option,
2791                which ICS uses to turn off password echoing when we are
2792                directly connected to it.  We reject this option
2793                if localLineEditing mode is on (always on in xboard)
2794                and we are talking to port 23, which might be a real
2795                telnet server that will try to keep WILL ECHO on permanently.
2796              */
2797             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2798                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2799                 unsigned char option;
2800                 oldi = i;
2801                 switch ((unsigned char) buf[++i]) {
2802                   case TN_WILL:
2803                     if (appData.debugMode)
2804                       fprintf(debugFP, "\n<WILL ");
2805                     switch (option = (unsigned char) buf[++i]) {
2806                       case TN_ECHO:
2807                         if (appData.debugMode)
2808                           fprintf(debugFP, "ECHO ");
2809                         /* Reply only if this is a change, according
2810                            to the protocol rules. */
2811                         if (remoteEchoOption) break;
2812                         if (appData.localLineEditing &&
2813                             atoi(appData.icsPort) == TN_PORT) {
2814                             TelnetRequest(TN_DONT, TN_ECHO);
2815                         } else {
2816                             EchoOff();
2817                             TelnetRequest(TN_DO, TN_ECHO);
2818                             remoteEchoOption = TRUE;
2819                         }
2820                         break;
2821                       default:
2822                         if (appData.debugMode)
2823                           fprintf(debugFP, "%d ", option);
2824                         /* Whatever this is, we don't want it. */
2825                         TelnetRequest(TN_DONT, option);
2826                         break;
2827                     }
2828                     break;
2829                   case TN_WONT:
2830                     if (appData.debugMode)
2831                       fprintf(debugFP, "\n<WONT ");
2832                     switch (option = (unsigned char) buf[++i]) {
2833                       case TN_ECHO:
2834                         if (appData.debugMode)
2835                           fprintf(debugFP, "ECHO ");
2836                         /* Reply only if this is a change, according
2837                            to the protocol rules. */
2838                         if (!remoteEchoOption) break;
2839                         EchoOn();
2840                         TelnetRequest(TN_DONT, TN_ECHO);
2841                         remoteEchoOption = FALSE;
2842                         break;
2843                       default:
2844                         if (appData.debugMode)
2845                           fprintf(debugFP, "%d ", (unsigned char) option);
2846                         /* Whatever this is, it must already be turned
2847                            off, because we never agree to turn on
2848                            anything non-default, so according to the
2849                            protocol rules, we don't reply. */
2850                         break;
2851                     }
2852                     break;
2853                   case TN_DO:
2854                     if (appData.debugMode)
2855                       fprintf(debugFP, "\n<DO ");
2856                     switch (option = (unsigned char) buf[++i]) {
2857                       default:
2858                         /* Whatever this is, we refuse to do it. */
2859                         if (appData.debugMode)
2860                           fprintf(debugFP, "%d ", option);
2861                         TelnetRequest(TN_WONT, option);
2862                         break;
2863                     }
2864                     break;
2865                   case TN_DONT:
2866                     if (appData.debugMode)
2867                       fprintf(debugFP, "\n<DONT ");
2868                     switch (option = (unsigned char) buf[++i]) {
2869                       default:
2870                         if (appData.debugMode)
2871                           fprintf(debugFP, "%d ", option);
2872                         /* Whatever this is, we are already not doing
2873                            it, because we never agree to do anything
2874                            non-default, so according to the protocol
2875                            rules, we don't reply. */
2876                         break;
2877                     }
2878                     break;
2879                   case TN_IAC:
2880                     if (appData.debugMode)
2881                       fprintf(debugFP, "\n<IAC ");
2882                     /* Doubled IAC; pass it through */
2883                     i--;
2884                     break;
2885                   default:
2886                     if (appData.debugMode)
2887                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2888                     /* Drop all other telnet commands on the floor */
2889                     break;
2890                 }
2891                 if (oldi > next_out)
2892                   SendToPlayer(&buf[next_out], oldi - next_out);
2893                 if (++i > next_out)
2894                   next_out = i;
2895                 continue;
2896             }
2897
2898             /* OK, this at least will *usually* work */
2899             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2900                 loggedOn = TRUE;
2901             }
2902
2903             if (loggedOn && !intfSet) {
2904                 if (ics_type == ICS_ICC) {
2905                   snprintf(str, MSG_SIZ,
2906                           "/set-quietly interface %s\n/set-quietly style 12\n",
2907                           programVersion);
2908                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2909                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2910                 } else if (ics_type == ICS_CHESSNET) {
2911                   snprintf(str, MSG_SIZ, "/style 12\n");
2912                 } else {
2913                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2914                   strcat(str, programVersion);
2915                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2916                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2917                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2918 #ifdef WIN32
2919                   strcat(str, "$iset nohighlight 1\n");
2920 #endif
2921                   strcat(str, "$iset lock 1\n$style 12\n");
2922                 }
2923                 SendToICS(str);
2924                 NotifyFrontendLogin();
2925                 intfSet = TRUE;
2926             }
2927
2928             if (started == STARTED_COMMENT) {
2929                 /* Accumulate characters in comment */
2930                 parse[parse_pos++] = buf[i];
2931                 if (buf[i] == '\n') {
2932                     parse[parse_pos] = NULLCHAR;
2933                     if(chattingPartner>=0) {
2934                         char mess[MSG_SIZ];
2935                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2936                         OutputChatMessage(chattingPartner, mess);
2937                         chattingPartner = -1;
2938                         next_out = i+1; // [HGM] suppress printing in ICS window
2939                     } else
2940                     if(!suppressKibitz) // [HGM] kibitz
2941                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2942                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2943                         int nrDigit = 0, nrAlph = 0, j;
2944                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2945                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2946                         parse[parse_pos] = NULLCHAR;
2947                         // try to be smart: if it does not look like search info, it should go to
2948                         // ICS interaction window after all, not to engine-output window.
2949                         for(j=0; j<parse_pos; j++) { // count letters and digits
2950                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2951                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2952                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2953                         }
2954                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2955                             int depth=0; float score;
2956                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2957                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2958                                 pvInfoList[forwardMostMove-1].depth = depth;
2959                                 pvInfoList[forwardMostMove-1].score = 100*score;
2960                             }
2961                             OutputKibitz(suppressKibitz, parse);
2962                         } else {
2963                             char tmp[MSG_SIZ];
2964                             if(gameMode == IcsObserving) // restore original ICS messages
2965                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2966                             else
2967                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2968                             SendToPlayer(tmp, strlen(tmp));
2969                         }
2970                         next_out = i+1; // [HGM] suppress printing in ICS window
2971                     }
2972                     started = STARTED_NONE;
2973                 } else {
2974                     /* Don't match patterns against characters in comment */
2975                     i++;
2976                     continue;
2977                 }
2978             }
2979             if (started == STARTED_CHATTER) {
2980                 if (buf[i] != '\n') {
2981                     /* Don't match patterns against characters in chatter */
2982                     i++;
2983                     continue;
2984                 }
2985                 started = STARTED_NONE;
2986                 if(suppressKibitz) next_out = i+1;
2987             }
2988
2989             /* Kludge to deal with rcmd protocol */
2990             if (firstTime && looking_at(buf, &i, "\001*")) {
2991                 DisplayFatalError(&buf[1], 0, 1);
2992                 continue;
2993             } else {
2994                 firstTime = FALSE;
2995             }
2996
2997             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2998                 ics_type = ICS_ICC;
2999                 ics_prefix = "/";
3000                 if (appData.debugMode)
3001                   fprintf(debugFP, "ics_type %d\n", ics_type);
3002                 continue;
3003             }
3004             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3005                 ics_type = ICS_FICS;
3006                 ics_prefix = "$";
3007                 if (appData.debugMode)
3008                   fprintf(debugFP, "ics_type %d\n", ics_type);
3009                 continue;
3010             }
3011             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3012                 ics_type = ICS_CHESSNET;
3013                 ics_prefix = "/";
3014                 if (appData.debugMode)
3015                   fprintf(debugFP, "ics_type %d\n", ics_type);
3016                 continue;
3017             }
3018
3019             if (!loggedOn &&
3020                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3021                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3022                  looking_at(buf, &i, "will be \"*\""))) {
3023               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3024               continue;
3025             }
3026
3027             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3028               char buf[MSG_SIZ];
3029               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3030               DisplayIcsInteractionTitle(buf);
3031               have_set_title = TRUE;
3032             }
3033
3034             /* skip finger notes */
3035             if (started == STARTED_NONE &&
3036                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3037                  (buf[i] == '1' && buf[i+1] == '0')) &&
3038                 buf[i+2] == ':' && buf[i+3] == ' ') {
3039               started = STARTED_CHATTER;
3040               i += 3;
3041               continue;
3042             }
3043
3044             oldi = i;
3045             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3046             if(appData.seekGraph) {
3047                 if(soughtPending && MatchSoughtLine(buf+i)) {
3048                     i = strstr(buf+i, "rated") - buf;
3049                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3050                     next_out = leftover_start = i;
3051                     started = STARTED_CHATTER;
3052                     suppressKibitz = TRUE;
3053                     continue;
3054                 }
3055                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3056                         && looking_at(buf, &i, "* ads displayed")) {
3057                     soughtPending = FALSE;
3058                     seekGraphUp = TRUE;
3059                     DrawSeekGraph();
3060                     continue;
3061                 }
3062                 if(appData.autoRefresh) {
3063                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3064                         int s = (ics_type == ICS_ICC); // ICC format differs
3065                         if(seekGraphUp)
3066                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3067                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3068                         looking_at(buf, &i, "*% "); // eat prompt
3069                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3070                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3071                         next_out = i; // suppress
3072                         continue;
3073                     }
3074                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3075                         char *p = star_match[0];
3076                         while(*p) {
3077                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3078                             while(*p && *p++ != ' '); // next
3079                         }
3080                         looking_at(buf, &i, "*% "); // eat prompt
3081                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3082                         next_out = i;
3083                         continue;
3084                     }
3085                 }
3086             }
3087
3088             /* skip formula vars */
3089             if (started == STARTED_NONE &&
3090                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3091               started = STARTED_CHATTER;
3092               i += 3;
3093               continue;
3094             }
3095
3096             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3097             if (appData.autoKibitz && started == STARTED_NONE &&
3098                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3099                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3100                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3101                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3102                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3103                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3104                         suppressKibitz = TRUE;
3105                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3106                         next_out = i;
3107                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3108                                 && (gameMode == IcsPlayingWhite)) ||
3109                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3110                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3111                             started = STARTED_CHATTER; // own kibitz we simply discard
3112                         else {
3113                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3114                             parse_pos = 0; parse[0] = NULLCHAR;
3115                             savingComment = TRUE;
3116                             suppressKibitz = gameMode != IcsObserving ? 2 :
3117                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3118                         }
3119                         continue;
3120                 } else
3121                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3122                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3123                          && atoi(star_match[0])) {
3124                     // suppress the acknowledgements of our own autoKibitz
3125                     char *p;
3126                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3127                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3128                     SendToPlayer(star_match[0], strlen(star_match[0]));
3129                     if(looking_at(buf, &i, "*% ")) // eat prompt
3130                         suppressKibitz = FALSE;
3131                     next_out = i;
3132                     continue;
3133                 }
3134             } // [HGM] kibitz: end of patch
3135
3136             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3137
3138             // [HGM] chat: intercept tells by users for which we have an open chat window
3139             channel = -1;
3140             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3141                                            looking_at(buf, &i, "* whispers:") ||
3142                                            looking_at(buf, &i, "* kibitzes:") ||
3143                                            looking_at(buf, &i, "* shouts:") ||
3144                                            looking_at(buf, &i, "* c-shouts:") ||
3145                                            looking_at(buf, &i, "--> * ") ||
3146                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3147                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3148                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3149                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3150                 int p;
3151                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3152                 chattingPartner = -1;
3153
3154                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3155                 for(p=0; p<MAX_CHAT; p++) {
3156                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3157                     talker[0] = '['; strcat(talker, "] ");
3158                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3159                     chattingPartner = p; break;
3160                     }
3161                 } else
3162                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3163                 for(p=0; p<MAX_CHAT; p++) {
3164                     if(!strcmp("kibitzes", chatPartner[p])) {
3165                         talker[0] = '['; strcat(talker, "] ");
3166                         chattingPartner = p; break;
3167                     }
3168                 } else
3169                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3170                 for(p=0; p<MAX_CHAT; p++) {
3171                     if(!strcmp("whispers", chatPartner[p])) {
3172                         talker[0] = '['; strcat(talker, "] ");
3173                         chattingPartner = p; break;
3174                     }
3175                 } else
3176                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3177                   if(buf[i-8] == '-' && buf[i-3] == 't')
3178                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3179                     if(!strcmp("c-shouts", chatPartner[p])) {
3180                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3181                         chattingPartner = p; break;
3182                     }
3183                   }
3184                   if(chattingPartner < 0)
3185                   for(p=0; p<MAX_CHAT; p++) {
3186                     if(!strcmp("shouts", chatPartner[p])) {
3187                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3188                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3189                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3190                         chattingPartner = p; break;
3191                     }
3192                   }
3193                 }
3194                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3195                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3196                     talker[0] = 0; Colorize(ColorTell, FALSE);
3197                     chattingPartner = p; break;
3198                 }
3199                 if(chattingPartner<0) i = oldi; else {
3200                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3201                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3202                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3203                     started = STARTED_COMMENT;
3204                     parse_pos = 0; parse[0] = NULLCHAR;
3205                     savingComment = 3 + chattingPartner; // counts as TRUE
3206                     suppressKibitz = TRUE;
3207                     continue;
3208                 }
3209             } // [HGM] chat: end of patch
3210
3211           backup = i;
3212             if (appData.zippyTalk || appData.zippyPlay) {
3213                 /* [DM] Backup address for color zippy lines */
3214 #if ZIPPY
3215                if (loggedOn == TRUE)
3216                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3217                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3218 #endif
3219             } // [DM] 'else { ' deleted
3220                 if (
3221                     /* Regular tells and says */
3222                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3223                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3224                     looking_at(buf, &i, "* says: ") ||
3225                     /* Don't color "message" or "messages" output */
3226                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3227                     looking_at(buf, &i, "*. * at *:*: ") ||
3228                     looking_at(buf, &i, "--* (*:*): ") ||
3229                     /* Message notifications (same color as tells) */
3230                     looking_at(buf, &i, "* has left a message ") ||
3231                     looking_at(buf, &i, "* just sent you a message:\n") ||
3232                     /* Whispers and kibitzes */
3233                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3234                     looking_at(buf, &i, "* kibitzes: ") ||
3235                     /* Channel tells */
3236                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3237
3238                   if (tkind == 1 && strchr(star_match[0], ':')) {
3239                       /* Avoid "tells you:" spoofs in channels */
3240                      tkind = 3;
3241                   }
3242                   if (star_match[0][0] == NULLCHAR ||
3243                       strchr(star_match[0], ' ') ||
3244                       (tkind == 3 && strchr(star_match[1], ' '))) {
3245                     /* Reject bogus matches */
3246                     i = oldi;
3247                   } else {
3248                     if (appData.colorize) {
3249                       if (oldi > next_out) {
3250                         SendToPlayer(&buf[next_out], oldi - next_out);
3251                         next_out = oldi;
3252                       }
3253                       switch (tkind) {
3254                       case 1:
3255                         Colorize(ColorTell, FALSE);
3256                         curColor = ColorTell;
3257                         break;
3258                       case 2:
3259                         Colorize(ColorKibitz, FALSE);
3260                         curColor = ColorKibitz;
3261                         break;
3262                       case 3:
3263                         p = strrchr(star_match[1], '(');
3264                         if (p == NULL) {
3265                           p = star_match[1];
3266                         } else {
3267                           p++;
3268                         }
3269                         if (atoi(p) == 1) {
3270                           Colorize(ColorChannel1, FALSE);
3271                           curColor = ColorChannel1;
3272                         } else {
3273                           Colorize(ColorChannel, FALSE);
3274                           curColor = ColorChannel;
3275                         }
3276                         break;
3277                       case 5:
3278                         curColor = ColorNormal;
3279                         break;
3280                       }
3281                     }
3282                     if (started == STARTED_NONE && appData.autoComment &&
3283                         (gameMode == IcsObserving ||
3284                          gameMode == IcsPlayingWhite ||
3285                          gameMode == IcsPlayingBlack)) {
3286                       parse_pos = i - oldi;
3287                       memcpy(parse, &buf[oldi], parse_pos);
3288                       parse[parse_pos] = NULLCHAR;
3289                       started = STARTED_COMMENT;
3290                       savingComment = TRUE;
3291                     } else {
3292                       started = STARTED_CHATTER;
3293                       savingComment = FALSE;
3294                     }
3295                     loggedOn = TRUE;
3296                     continue;
3297                   }
3298                 }
3299
3300                 if (looking_at(buf, &i, "* s-shouts: ") ||
3301                     looking_at(buf, &i, "* c-shouts: ")) {
3302                     if (appData.colorize) {
3303                         if (oldi > next_out) {
3304                             SendToPlayer(&buf[next_out], oldi - next_out);
3305                             next_out = oldi;
3306                         }
3307                         Colorize(ColorSShout, FALSE);
3308                         curColor = ColorSShout;
3309                     }
3310                     loggedOn = TRUE;
3311                     started = STARTED_CHATTER;
3312                     continue;
3313                 }
3314
3315                 if (looking_at(buf, &i, "--->")) {
3316                     loggedOn = TRUE;
3317                     continue;
3318                 }
3319
3320                 if (looking_at(buf, &i, "* shouts: ") ||
3321                     looking_at(buf, &i, "--> ")) {
3322                     if (appData.colorize) {
3323                         if (oldi > next_out) {
3324                             SendToPlayer(&buf[next_out], oldi - next_out);
3325                             next_out = oldi;
3326                         }
3327                         Colorize(ColorShout, FALSE);
3328                         curColor = ColorShout;
3329                     }
3330                     loggedOn = TRUE;
3331                     started = STARTED_CHATTER;
3332                     continue;
3333                 }
3334
3335                 if (looking_at( buf, &i, "Challenge:")) {
3336                     if (appData.colorize) {
3337                         if (oldi > next_out) {
3338                             SendToPlayer(&buf[next_out], oldi - next_out);
3339                             next_out = oldi;
3340                         }
3341                         Colorize(ColorChallenge, FALSE);
3342                         curColor = ColorChallenge;
3343                     }
3344                     loggedOn = TRUE;
3345                     continue;
3346                 }
3347
3348                 if (looking_at(buf, &i, "* offers you") ||
3349                     looking_at(buf, &i, "* offers to be") ||
3350                     looking_at(buf, &i, "* would like to") ||
3351                     looking_at(buf, &i, "* requests to") ||
3352                     looking_at(buf, &i, "Your opponent offers") ||
3353                     looking_at(buf, &i, "Your opponent requests")) {
3354
3355                     if (appData.colorize) {
3356                         if (oldi > next_out) {
3357                             SendToPlayer(&buf[next_out], oldi - next_out);
3358                             next_out = oldi;
3359                         }
3360                         Colorize(ColorRequest, FALSE);
3361                         curColor = ColorRequest;
3362                     }
3363                     continue;
3364                 }
3365
3366                 if (looking_at(buf, &i, "* (*) seeking")) {
3367                     if (appData.colorize) {
3368                         if (oldi > next_out) {
3369                             SendToPlayer(&buf[next_out], oldi - next_out);
3370                             next_out = oldi;
3371                         }
3372                         Colorize(ColorSeek, FALSE);
3373                         curColor = ColorSeek;
3374                     }
3375                     continue;
3376             }
3377
3378           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3379
3380             if (looking_at(buf, &i, "\\   ")) {
3381                 if (prevColor != ColorNormal) {
3382                     if (oldi > next_out) {
3383                         SendToPlayer(&buf[next_out], oldi - next_out);
3384                         next_out = oldi;
3385                     }
3386                     Colorize(prevColor, TRUE);
3387                     curColor = prevColor;
3388                 }
3389                 if (savingComment) {
3390                     parse_pos = i - oldi;
3391                     memcpy(parse, &buf[oldi], parse_pos);
3392                     parse[parse_pos] = NULLCHAR;
3393                     started = STARTED_COMMENT;
3394                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3395                         chattingPartner = savingComment - 3; // kludge to remember the box
3396                 } else {
3397                     started = STARTED_CHATTER;
3398                 }
3399                 continue;
3400             }
3401
3402             if (looking_at(buf, &i, "Black Strength :") ||
3403                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3404                 looking_at(buf, &i, "<10>") ||
3405                 looking_at(buf, &i, "#@#")) {
3406                 /* Wrong board style */
3407                 loggedOn = TRUE;
3408                 SendToICS(ics_prefix);
3409                 SendToICS("set style 12\n");
3410                 SendToICS(ics_prefix);
3411                 SendToICS("refresh\n");
3412                 continue;
3413             }
3414
3415             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3416                 ICSInitScript();
3417                 have_sent_ICS_logon = 1;
3418                 continue;
3419             }
3420
3421             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3422                 (looking_at(buf, &i, "\n<12> ") ||
3423                  looking_at(buf, &i, "<12> "))) {
3424                 loggedOn = TRUE;
3425                 if (oldi > next_out) {
3426                     SendToPlayer(&buf[next_out], oldi - next_out);
3427                 }
3428                 next_out = i;
3429                 started = STARTED_BOARD;
3430                 parse_pos = 0;
3431                 continue;
3432             }
3433
3434             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3435                 looking_at(buf, &i, "<b1> ")) {
3436                 if (oldi > next_out) {
3437                     SendToPlayer(&buf[next_out], oldi - next_out);
3438                 }
3439                 next_out = i;
3440                 started = STARTED_HOLDINGS;
3441                 parse_pos = 0;
3442                 continue;
3443             }
3444
3445             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3446                 loggedOn = TRUE;
3447                 /* Header for a move list -- first line */
3448
3449                 switch (ics_getting_history) {
3450                   case H_FALSE:
3451                     switch (gameMode) {
3452                       case IcsIdle:
3453                       case BeginningOfGame:
3454                         /* User typed "moves" or "oldmoves" while we
3455                            were idle.  Pretend we asked for these
3456                            moves and soak them up so user can step
3457                            through them and/or save them.
3458                            */
3459                         Reset(FALSE, TRUE);
3460                         gameMode = IcsObserving;
3461                         ModeHighlight();
3462                         ics_gamenum = -1;
3463                         ics_getting_history = H_GOT_UNREQ_HEADER;
3464                         break;
3465                       case EditGame: /*?*/
3466                       case EditPosition: /*?*/
3467                         /* Should above feature work in these modes too? */
3468                         /* For now it doesn't */
3469                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3470                         break;
3471                       default:
3472                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3473                         break;
3474                     }
3475                     break;
3476                   case H_REQUESTED:
3477                     /* Is this the right one? */
3478                     if (gameInfo.white && gameInfo.black &&
3479                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3480                         strcmp(gameInfo.black, star_match[2]) == 0) {
3481                         /* All is well */
3482                         ics_getting_history = H_GOT_REQ_HEADER;
3483                     }
3484                     break;
3485                   case H_GOT_REQ_HEADER:
3486                   case H_GOT_UNREQ_HEADER:
3487                   case H_GOT_UNWANTED_HEADER:
3488                   case H_GETTING_MOVES:
3489                     /* Should not happen */
3490                     DisplayError(_("Error gathering move list: two headers"), 0);
3491                     ics_getting_history = H_FALSE;
3492                     break;
3493                 }
3494
3495                 /* Save player ratings into gameInfo if needed */
3496                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3497                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3498                     (gameInfo.whiteRating == -1 ||
3499                      gameInfo.blackRating == -1)) {
3500
3501                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3502                     gameInfo.blackRating = string_to_rating(star_match[3]);
3503                     if (appData.debugMode)
3504                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3505                               gameInfo.whiteRating, gameInfo.blackRating);
3506                 }
3507                 continue;
3508             }
3509
3510             if (looking_at(buf, &i,
3511               "* * match, initial time: * minute*, increment: * second")) {
3512                 /* Header for a move list -- second line */
3513                 /* Initial board will follow if this is a wild game */
3514                 if (gameInfo.event != NULL) free(gameInfo.event);
3515                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3516                 gameInfo.event = StrSave(str);
3517                 /* [HGM] we switched variant. Translate boards if needed. */
3518                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3519                 continue;
3520             }
3521
3522             if (looking_at(buf, &i, "Move  ")) {
3523                 /* Beginning of a move list */
3524                 switch (ics_getting_history) {
3525                   case H_FALSE:
3526                     /* Normally should not happen */
3527                     /* Maybe user hit reset while we were parsing */
3528                     break;
3529                   case H_REQUESTED:
3530                     /* Happens if we are ignoring a move list that is not
3531                      * the one we just requested.  Common if the user
3532                      * tries to observe two games without turning off
3533                      * getMoveList */
3534                     break;
3535                   case H_GETTING_MOVES:
3536                     /* Should not happen */
3537                     DisplayError(_("Error gathering move list: nested"), 0);
3538                     ics_getting_history = H_FALSE;
3539                     break;
3540                   case H_GOT_REQ_HEADER:
3541                     ics_getting_history = H_GETTING_MOVES;
3542                     started = STARTED_MOVES;
3543                     parse_pos = 0;
3544                     if (oldi > next_out) {
3545                         SendToPlayer(&buf[next_out], oldi - next_out);
3546                     }
3547                     break;
3548                   case H_GOT_UNREQ_HEADER:
3549                     ics_getting_history = H_GETTING_MOVES;
3550                     started = STARTED_MOVES_NOHIDE;
3551                     parse_pos = 0;
3552                     break;
3553                   case H_GOT_UNWANTED_HEADER:
3554                     ics_getting_history = H_FALSE;
3555                     break;
3556                 }
3557                 continue;
3558             }
3559
3560             if (looking_at(buf, &i, "% ") ||
3561                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3562                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3563                 if(soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3564                     soughtPending = FALSE;
3565                     seekGraphUp = TRUE;
3566                     DrawSeekGraph();
3567                 }
3568                 if(suppressKibitz) next_out = i;
3569                 savingComment = FALSE;
3570                 suppressKibitz = 0;
3571                 switch (started) {
3572                   case STARTED_MOVES:
3573                   case STARTED_MOVES_NOHIDE:
3574                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3575                     parse[parse_pos + i - oldi] = NULLCHAR;
3576                     ParseGameHistory(parse);
3577 #if ZIPPY
3578                     if (appData.zippyPlay && first.initDone) {
3579                         FeedMovesToProgram(&first, forwardMostMove);
3580                         if (gameMode == IcsPlayingWhite) {
3581                             if (WhiteOnMove(forwardMostMove)) {
3582                                 if (first.sendTime) {
3583                                   if (first.useColors) {
3584                                     SendToProgram("black\n", &first);
3585                                   }
3586                                   SendTimeRemaining(&first, TRUE);
3587                                 }
3588                                 if (first.useColors) {
3589                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3590                                 }
3591                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3592                                 first.maybeThinking = TRUE;
3593                             } else {
3594                                 if (first.usePlayother) {
3595                                   if (first.sendTime) {
3596                                     SendTimeRemaining(&first, TRUE);
3597                                   }
3598                                   SendToProgram("playother\n", &first);
3599                                   firstMove = FALSE;
3600                                 } else {
3601                                   firstMove = TRUE;
3602                                 }
3603                             }
3604                         } else if (gameMode == IcsPlayingBlack) {
3605                             if (!WhiteOnMove(forwardMostMove)) {
3606                                 if (first.sendTime) {
3607                                   if (first.useColors) {
3608                                     SendToProgram("white\n", &first);
3609                                   }
3610                                   SendTimeRemaining(&first, FALSE);
3611                                 }
3612                                 if (first.useColors) {
3613                                   SendToProgram("black\n", &first);
3614                                 }
3615                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3616                                 first.maybeThinking = TRUE;
3617                             } else {
3618                                 if (first.usePlayother) {
3619                                   if (first.sendTime) {
3620                                     SendTimeRemaining(&first, FALSE);
3621                                   }
3622                                   SendToProgram("playother\n", &first);
3623                                   firstMove = FALSE;
3624                                 } else {
3625                                   firstMove = TRUE;
3626                                 }
3627                             }
3628                         }
3629                     }
3630 #endif
3631                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3632                         /* Moves came from oldmoves or moves command
3633                            while we weren't doing anything else.
3634                            */
3635                         currentMove = forwardMostMove;
3636                         ClearHighlights();/*!!could figure this out*/
3637                         flipView = appData.flipView;
3638                         DrawPosition(TRUE, boards[currentMove]);
3639                         DisplayBothClocks();
3640                         snprintf(str, MSG_SIZ, "%s %s %s",
3641                                 gameInfo.white, _("vs."),  gameInfo.black);
3642                         DisplayTitle(str);
3643                         gameMode = IcsIdle;
3644                     } else {
3645                         /* Moves were history of an active game */
3646                         if (gameInfo.resultDetails != NULL) {
3647                             free(gameInfo.resultDetails);
3648                             gameInfo.resultDetails = NULL;
3649                         }
3650                     }
3651                     HistorySet(parseList, backwardMostMove,
3652                                forwardMostMove, currentMove-1);
3653                     DisplayMove(currentMove - 1);
3654                     if (started == STARTED_MOVES) next_out = i;
3655                     started = STARTED_NONE;
3656                     ics_getting_history = H_FALSE;
3657                     break;
3658
3659                   case STARTED_OBSERVE:
3660                     started = STARTED_NONE;
3661                     SendToICS(ics_prefix);
3662                     SendToICS("refresh\n");
3663                     break;
3664
3665                   default:
3666                     break;
3667                 }
3668                 if(bookHit) { // [HGM] book: simulate book reply
3669                     static char bookMove[MSG_SIZ]; // a bit generous?
3670
3671                     programStats.nodes = programStats.depth = programStats.time =
3672                     programStats.score = programStats.got_only_move = 0;
3673                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3674
3675                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3676                     strcat(bookMove, bookHit);
3677                     HandleMachineMove(bookMove, &first);
3678                 }
3679                 continue;
3680             }
3681
3682             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3683                  started == STARTED_HOLDINGS ||
3684                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3685                 /* Accumulate characters in move list or board */
3686                 parse[parse_pos++] = buf[i];
3687             }
3688
3689             /* Start of game messages.  Mostly we detect start of game
3690                when the first board image arrives.  On some versions
3691                of the ICS, though, we need to do a "refresh" after starting
3692                to observe in order to get the current board right away. */
3693             if (looking_at(buf, &i, "Adding game * to observation list")) {
3694                 started = STARTED_OBSERVE;
3695                 continue;
3696             }
3697
3698             /* Handle auto-observe */
3699             if (appData.autoObserve &&
3700                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3701                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3702                 char *player;
3703                 /* Choose the player that was highlighted, if any. */
3704                 if (star_match[0][0] == '\033' ||
3705                     star_match[1][0] != '\033') {
3706                     player = star_match[0];
3707                 } else {
3708                     player = star_match[2];
3709                 }
3710                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3711                         ics_prefix, StripHighlightAndTitle(player));
3712                 SendToICS(str);
3713
3714                 /* Save ratings from notify string */
3715                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3716                 player1Rating = string_to_rating(star_match[1]);
3717                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3718                 player2Rating = string_to_rating(star_match[3]);
3719
3720                 if (appData.debugMode)
3721                   fprintf(debugFP,
3722                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3723                           player1Name, player1Rating,
3724                           player2Name, player2Rating);
3725
3726                 continue;
3727             }
3728
3729             /* Deal with automatic examine mode after a game,
3730                and with IcsObserving -> IcsExamining transition */
3731             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3732                 looking_at(buf, &i, "has made you an examiner of game *")) {
3733
3734                 int gamenum = atoi(star_match[0]);
3735                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3736                     gamenum == ics_gamenum) {
3737                     /* We were already playing or observing this game;
3738                        no need to refetch history */
3739                     gameMode = IcsExamining;
3740                     if (pausing) {
3741                         pauseExamForwardMostMove = forwardMostMove;
3742                     } else if (currentMove < forwardMostMove) {
3743                         ForwardInner(forwardMostMove);
3744                     }
3745                 } else {
3746                     /* I don't think this case really can happen */
3747                     SendToICS(ics_prefix);
3748                     SendToICS("refresh\n");
3749                 }
3750                 continue;
3751             }
3752
3753             /* Error messages */
3754 //          if (ics_user_moved) {
3755             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3756                 if (looking_at(buf, &i, "Illegal move") ||
3757                     looking_at(buf, &i, "Not a legal move") ||
3758                     looking_at(buf, &i, "Your king is in check") ||
3759                     looking_at(buf, &i, "It isn't your turn") ||
3760                     looking_at(buf, &i, "It is not your move")) {
3761                     /* Illegal move */
3762                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3763                         currentMove = forwardMostMove-1;
3764                         DisplayMove(currentMove - 1); /* before DMError */
3765                         DrawPosition(FALSE, boards[currentMove]);
3766                         SwitchClocks(forwardMostMove-1); // [HGM] race
3767                         DisplayBothClocks();
3768                     }
3769                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3770                     ics_user_moved = 0;
3771                     continue;
3772                 }
3773             }
3774
3775             if (looking_at(buf, &i, "still have time") ||
3776                 looking_at(buf, &i, "not out of time") ||
3777                 looking_at(buf, &i, "either player is out of time") ||
3778                 looking_at(buf, &i, "has timeseal; checking")) {
3779                 /* We must have called his flag a little too soon */
3780                 whiteFlag = blackFlag = FALSE;
3781                 continue;
3782             }
3783
3784             if (looking_at(buf, &i, "added * seconds to") ||
3785                 looking_at(buf, &i, "seconds were added to")) {
3786                 /* Update the clocks */
3787                 SendToICS(ics_prefix);
3788                 SendToICS("refresh\n");
3789                 continue;
3790             }
3791
3792             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3793                 ics_clock_paused = TRUE;
3794                 StopClocks();
3795                 continue;
3796             }
3797
3798             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3799                 ics_clock_paused = FALSE;
3800                 StartClocks();
3801                 continue;
3802             }
3803
3804             /* Grab player ratings from the Creating: message.
3805                Note we have to check for the special case when
3806                the ICS inserts things like [white] or [black]. */
3807             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3808                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3809                 /* star_matches:
3810                    0    player 1 name (not necessarily white)
3811                    1    player 1 rating
3812                    2    empty, white, or black (IGNORED)
3813                    3    player 2 name (not necessarily black)
3814                    4    player 2 rating
3815
3816                    The names/ratings are sorted out when the game
3817                    actually starts (below).
3818                 */
3819                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3820                 player1Rating = string_to_rating(star_match[1]);
3821                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3822                 player2Rating = string_to_rating(star_match[4]);
3823
3824                 if (appData.debugMode)
3825                   fprintf(debugFP,
3826                           "Ratings from 'Creating:' %s %d, %s %d\n",
3827                           player1Name, player1Rating,
3828                           player2Name, player2Rating);
3829
3830                 continue;
3831             }
3832
3833             /* Improved generic start/end-of-game messages */
3834             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3835                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3836                 /* If tkind == 0: */
3837                 /* star_match[0] is the game number */
3838                 /*           [1] is the white player's name */
3839                 /*           [2] is the black player's name */
3840                 /* For end-of-game: */
3841                 /*           [3] is the reason for the game end */
3842                 /*           [4] is a PGN end game-token, preceded by " " */
3843                 /* For start-of-game: */
3844                 /*           [3] begins with "Creating" or "Continuing" */
3845                 /*           [4] is " *" or empty (don't care). */
3846                 int gamenum = atoi(star_match[0]);
3847                 char *whitename, *blackname, *why, *endtoken;
3848                 ChessMove endtype = EndOfFile;
3849
3850                 if (tkind == 0) {
3851                   whitename = star_match[1];
3852                   blackname = star_match[2];
3853                   why = star_match[3];
3854                   endtoken = star_match[4];
3855                 } else {
3856                   whitename = star_match[1];
3857                   blackname = star_match[3];
3858                   why = star_match[5];
3859                   endtoken = star_match[6];
3860                 }
3861
3862                 /* Game start messages */
3863                 if (strncmp(why, "Creating ", 9) == 0 ||
3864                     strncmp(why, "Continuing ", 11) == 0) {
3865                     gs_gamenum = gamenum;
3866                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3867                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3868                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3869 #if ZIPPY
3870                     if (appData.zippyPlay) {
3871                         ZippyGameStart(whitename, blackname);
3872                     }
3873 #endif /*ZIPPY*/
3874                     partnerBoardValid = FALSE; // [HGM] bughouse
3875                     continue;
3876                 }
3877
3878                 /* Game end messages */
3879                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3880                     ics_gamenum != gamenum) {
3881                     continue;
3882                 }
3883                 while (endtoken[0] == ' ') endtoken++;
3884                 switch (endtoken[0]) {
3885                   case '*':
3886                   default:
3887                     endtype = GameUnfinished;
3888                     break;
3889                   case '0':
3890                     endtype = BlackWins;
3891                     break;
3892                   case '1':
3893                     if (endtoken[1] == '/')
3894                       endtype = GameIsDrawn;
3895                     else
3896                       endtype = WhiteWins;
3897                     break;
3898                 }
3899                 GameEnds(endtype, why, GE_ICS);
3900 #if ZIPPY
3901                 if (appData.zippyPlay && first.initDone) {
3902                     ZippyGameEnd(endtype, why);
3903                     if (first.pr == NoProc) {
3904                       /* Start the next process early so that we'll
3905                          be ready for the next challenge */
3906                       StartChessProgram(&first);
3907                     }
3908                     /* Send "new" early, in case this command takes
3909                        a long time to finish, so that we'll be ready
3910                        for the next challenge. */
3911                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3912                     Reset(TRUE, TRUE);
3913                 }
3914 #endif /*ZIPPY*/
3915                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3916                 continue;
3917             }
3918
3919             if (looking_at(buf, &i, "Removing game * from observation") ||
3920                 looking_at(buf, &i, "no longer observing game *") ||
3921                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3922                 if (gameMode == IcsObserving &&
3923                     atoi(star_match[0]) == ics_gamenum)
3924                   {
3925                       /* icsEngineAnalyze */
3926                       if (appData.icsEngineAnalyze) {
3927                             ExitAnalyzeMode();
3928                             ModeHighlight();
3929                       }
3930                       StopClocks();
3931                       gameMode = IcsIdle;
3932                       ics_gamenum = -1;
3933                       ics_user_moved = FALSE;
3934                   }
3935                 continue;
3936             }
3937
3938             if (looking_at(buf, &i, "no longer examining game *")) {
3939                 if (gameMode == IcsExamining &&
3940                     atoi(star_match[0]) == ics_gamenum)
3941                   {
3942                       gameMode = IcsIdle;
3943                       ics_gamenum = -1;
3944                       ics_user_moved = FALSE;
3945                   }
3946                 continue;
3947             }
3948
3949             /* Advance leftover_start past any newlines we find,
3950                so only partial lines can get reparsed */
3951             if (looking_at(buf, &i, "\n")) {
3952                 prevColor = curColor;
3953                 if (curColor != ColorNormal) {
3954                     if (oldi > next_out) {
3955                         SendToPlayer(&buf[next_out], oldi - next_out);
3956                         next_out = oldi;
3957                     }
3958                     Colorize(ColorNormal, FALSE);
3959                     curColor = ColorNormal;
3960                 }
3961                 if (started == STARTED_BOARD) {
3962                     started = STARTED_NONE;
3963                     parse[parse_pos] = NULLCHAR;
3964                     ParseBoard12(parse);
3965                     ics_user_moved = 0;
3966
3967                     /* Send premove here */
3968                     if (appData.premove) {
3969                       char str[MSG_SIZ];
3970                       if (currentMove == 0 &&
3971                           gameMode == IcsPlayingWhite &&
3972                           appData.premoveWhite) {
3973                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3974                         if (appData.debugMode)
3975                           fprintf(debugFP, "Sending premove:\n");
3976                         SendToICS(str);
3977                       } else if (currentMove == 1 &&
3978                                  gameMode == IcsPlayingBlack &&
3979                                  appData.premoveBlack) {
3980                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3981                         if (appData.debugMode)
3982                           fprintf(debugFP, "Sending premove:\n");
3983                         SendToICS(str);
3984                       } else if (gotPremove) {
3985                         gotPremove = 0;
3986                         ClearPremoveHighlights();
3987                         if (appData.debugMode)
3988                           fprintf(debugFP, "Sending premove:\n");
3989                           UserMoveEvent(premoveFromX, premoveFromY,
3990                                         premoveToX, premoveToY,
3991                                         premovePromoChar);
3992                       }
3993                     }
3994
3995                     /* Usually suppress following prompt */
3996                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3997                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3998                         if (looking_at(buf, &i, "*% ")) {
3999                             savingComment = FALSE;
4000                             suppressKibitz = 0;
4001                         }
4002                     }
4003                     next_out = i;
4004                 } else if (started == STARTED_HOLDINGS) {
4005                     int gamenum;
4006                     char new_piece[MSG_SIZ];
4007                     started = STARTED_NONE;
4008                     parse[parse_pos] = NULLCHAR;
4009                     if (appData.debugMode)
4010                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4011                                                         parse, currentMove);
4012                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4013                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4014                         if (gameInfo.variant == VariantNormal) {
4015                           /* [HGM] We seem to switch variant during a game!
4016                            * Presumably no holdings were displayed, so we have
4017                            * to move the position two files to the right to
4018                            * create room for them!
4019                            */
4020                           VariantClass newVariant;
4021                           switch(gameInfo.boardWidth) { // base guess on board width
4022                                 case 9:  newVariant = VariantShogi; break;
4023                                 case 10: newVariant = VariantGreat; break;
4024                                 default: newVariant = VariantCrazyhouse; break;
4025                           }
4026                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4027                           /* Get a move list just to see the header, which
4028                              will tell us whether this is really bug or zh */
4029                           if (ics_getting_history == H_FALSE) {
4030                             ics_getting_history = H_REQUESTED;
4031                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4032                             SendToICS(str);
4033                           }
4034                         }
4035                         new_piece[0] = NULLCHAR;
4036                         sscanf(parse, "game %d white [%s black [%s <- %s",
4037                                &gamenum, white_holding, black_holding,
4038                                new_piece);
4039                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4040                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4041                         /* [HGM] copy holdings to board holdings area */
4042                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4043                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4044                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4045 #if ZIPPY
4046                         if (appData.zippyPlay && first.initDone) {
4047                             ZippyHoldings(white_holding, black_holding,
4048                                           new_piece);
4049                         }
4050 #endif /*ZIPPY*/
4051                         if (tinyLayout || smallLayout) {
4052                             char wh[16], bh[16];
4053                             PackHolding(wh, white_holding);
4054                             PackHolding(bh, black_holding);
4055                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4056                                     gameInfo.white, gameInfo.black);
4057                         } else {
4058                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4059                                     gameInfo.white, white_holding, _("vs."),
4060                                     gameInfo.black, black_holding);
4061                         }
4062                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4063                         DrawPosition(FALSE, boards[currentMove]);
4064                         DisplayTitle(str);
4065                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4066                         sscanf(parse, "game %d white [%s black [%s <- %s",
4067                                &gamenum, white_holding, black_holding,
4068                                new_piece);
4069                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4070                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4071                         /* [HGM] copy holdings to partner-board holdings area */
4072                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4073                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4074                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4075                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4076                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4077                       }
4078                     }
4079                     /* Suppress following prompt */
4080                     if (looking_at(buf, &i, "*% ")) {
4081                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4082                         savingComment = FALSE;
4083                         suppressKibitz = 0;
4084                     }
4085                     next_out = i;
4086                 }
4087                 continue;
4088             }
4089
4090             i++;                /* skip unparsed character and loop back */
4091         }
4092
4093         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4094 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4095 //          SendToPlayer(&buf[next_out], i - next_out);
4096             started != STARTED_HOLDINGS && leftover_start > next_out) {
4097             SendToPlayer(&buf[next_out], leftover_start - next_out);
4098             next_out = i;
4099         }
4100
4101         leftover_len = buf_len - leftover_start;
4102         /* if buffer ends with something we couldn't parse,
4103            reparse it after appending the next read */
4104
4105     } else if (count == 0) {
4106         RemoveInputSource(isr);
4107         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4108     } else {
4109         DisplayFatalError(_("Error reading from ICS"), error, 1);
4110     }
4111 }
4112
4113
4114 /* Board style 12 looks like this:
4115
4116    <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
4117
4118  * The "<12> " is stripped before it gets to this routine.  The two
4119  * trailing 0's (flip state and clock ticking) are later addition, and
4120  * some chess servers may not have them, or may have only the first.
4121  * Additional trailing fields may be added in the future.
4122  */
4123
4124 #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"
4125
4126 #define RELATION_OBSERVING_PLAYED    0
4127 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4128 #define RELATION_PLAYING_MYMOVE      1
4129 #define RELATION_PLAYING_NOTMYMOVE  -1
4130 #define RELATION_EXAMINING           2
4131 #define RELATION_ISOLATED_BOARD     -3
4132 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4133
4134 void
4135 ParseBoard12 (char *string)
4136 {
4137     GameMode newGameMode;
4138     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4139     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4140     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4141     char to_play, board_chars[200];
4142     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4143     char black[32], white[32];
4144     Board board;
4145     int prevMove = currentMove;
4146     int ticking = 2;
4147     ChessMove moveType;
4148     int fromX, fromY, toX, toY;
4149     char promoChar;
4150     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4151     char *bookHit = NULL; // [HGM] book
4152     Boolean weird = FALSE, reqFlag = FALSE;
4153
4154     fromX = fromY = toX = toY = -1;
4155
4156     newGame = FALSE;
4157
4158     if (appData.debugMode)
4159       fprintf(debugFP, _("Parsing board: %s\n"), string);
4160
4161     move_str[0] = NULLCHAR;
4162     elapsed_time[0] = NULLCHAR;
4163     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4164         int  i = 0, j;
4165         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4166             if(string[i] == ' ') { ranks++; files = 0; }
4167             else files++;
4168             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4169             i++;
4170         }
4171         for(j = 0; j <i; j++) board_chars[j] = string[j];
4172         board_chars[i] = '\0';
4173         string += i + 1;
4174     }
4175     n = sscanf(string, PATTERN, &to_play, &double_push,
4176                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4177                &gamenum, white, black, &relation, &basetime, &increment,
4178                &white_stren, &black_stren, &white_time, &black_time,
4179                &moveNum, str, elapsed_time, move_str, &ics_flip,
4180                &ticking);
4181
4182     if (n < 21) {
4183         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4184         DisplayError(str, 0);
4185         return;
4186     }
4187
4188     /* Convert the move number to internal form */
4189     moveNum = (moveNum - 1) * 2;
4190     if (to_play == 'B') moveNum++;
4191     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4192       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4193                         0, 1);
4194       return;
4195     }
4196
4197     switch (relation) {
4198       case RELATION_OBSERVING_PLAYED:
4199       case RELATION_OBSERVING_STATIC:
4200         if (gamenum == -1) {
4201             /* Old ICC buglet */
4202             relation = RELATION_OBSERVING_STATIC;
4203         }
4204         newGameMode = IcsObserving;
4205         break;
4206       case RELATION_PLAYING_MYMOVE:
4207       case RELATION_PLAYING_NOTMYMOVE:
4208         newGameMode =
4209           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4210             IcsPlayingWhite : IcsPlayingBlack;
4211         break;
4212       case RELATION_EXAMINING:
4213         newGameMode = IcsExamining;
4214         break;
4215       case RELATION_ISOLATED_BOARD:
4216       default:
4217         /* Just display this board.  If user was doing something else,
4218            we will forget about it until the next board comes. */
4219         newGameMode = IcsIdle;
4220         break;
4221       case RELATION_STARTING_POSITION:
4222         newGameMode = gameMode;
4223         break;
4224     }
4225
4226     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4227          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4228       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4229       char *toSqr;
4230       for (k = 0; k < ranks; k++) {
4231         for (j = 0; j < files; j++)
4232           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4233         if(gameInfo.holdingsWidth > 1) {
4234              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4235              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4236         }
4237       }
4238       CopyBoard(partnerBoard, board);
4239       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4240         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4241         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4242       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4243       if(toSqr = strchr(str, '-')) {
4244         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4245         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4246       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4247       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4248       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4249       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4250       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4251       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4252                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4253       DisplayMessage(partnerStatus, "");
4254         partnerBoardValid = TRUE;
4255       return;
4256     }
4257
4258     /* Modify behavior for initial board display on move listing
4259        of wild games.
4260        */
4261     switch (ics_getting_history) {
4262       case H_FALSE:
4263       case H_REQUESTED:
4264         break;
4265       case H_GOT_REQ_HEADER:
4266       case H_GOT_UNREQ_HEADER:
4267         /* This is the initial position of the current game */
4268         gamenum = ics_gamenum;
4269         moveNum = 0;            /* old ICS bug workaround */
4270         if (to_play == 'B') {
4271           startedFromSetupPosition = TRUE;
4272           blackPlaysFirst = TRUE;
4273           moveNum = 1;
4274           if (forwardMostMove == 0) forwardMostMove = 1;
4275           if (backwardMostMove == 0) backwardMostMove = 1;
4276           if (currentMove == 0) currentMove = 1;
4277         }
4278         newGameMode = gameMode;
4279         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4280         break;
4281       case H_GOT_UNWANTED_HEADER:
4282         /* This is an initial board that we don't want */
4283         return;
4284       case H_GETTING_MOVES:
4285         /* Should not happen */
4286         DisplayError(_("Error gathering move list: extra board"), 0);
4287         ics_getting_history = H_FALSE;
4288         return;
4289     }
4290
4291    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4292                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4293      /* [HGM] We seem to have switched variant unexpectedly
4294       * Try to guess new variant from board size
4295       */
4296           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4297           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4298           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4299           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4300           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4301           if(!weird) newVariant = VariantNormal;
4302           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4303           /* Get a move list just to see the header, which
4304              will tell us whether this is really bug or zh */
4305           if (ics_getting_history == H_FALSE) {
4306             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4307             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4308             SendToICS(str);
4309           }
4310     }
4311
4312     /* Take action if this is the first board of a new game, or of a
4313        different game than is currently being displayed.  */
4314     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4315         relation == RELATION_ISOLATED_BOARD) {
4316
4317         /* Forget the old game and get the history (if any) of the new one */
4318         if (gameMode != BeginningOfGame) {
4319           Reset(TRUE, TRUE);
4320         }
4321         newGame = TRUE;
4322         if (appData.autoRaiseBoard) BoardToTop();
4323         prevMove = -3;
4324         if (gamenum == -1) {
4325             newGameMode = IcsIdle;
4326         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4327                    appData.getMoveList && !reqFlag) {
4328             /* Need to get game history */
4329             ics_getting_history = H_REQUESTED;
4330             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4331             SendToICS(str);
4332         }
4333
4334         /* Initially flip the board to have black on the bottom if playing
4335            black or if the ICS flip flag is set, but let the user change
4336            it with the Flip View button. */
4337         flipView = appData.autoFlipView ?
4338           (newGameMode == IcsPlayingBlack) || ics_flip :
4339           appData.flipView;
4340
4341         /* Done with values from previous mode; copy in new ones */
4342         gameMode = newGameMode;
4343         ModeHighlight();
4344         ics_gamenum = gamenum;
4345         if (gamenum == gs_gamenum) {
4346             int klen = strlen(gs_kind);
4347             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4348             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4349             gameInfo.event = StrSave(str);
4350         } else {
4351             gameInfo.event = StrSave("ICS game");
4352         }
4353         gameInfo.site = StrSave(appData.icsHost);
4354         gameInfo.date = PGNDate();
4355         gameInfo.round = StrSave("-");
4356         gameInfo.white = StrSave(white);
4357         gameInfo.black = StrSave(black);
4358         timeControl = basetime * 60 * 1000;
4359         timeControl_2 = 0;
4360         timeIncrement = increment * 1000;
4361         movesPerSession = 0;
4362         gameInfo.timeControl = TimeControlTagValue();
4363         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4364   if (appData.debugMode) {
4365     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4366     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4367     setbuf(debugFP, NULL);
4368   }
4369
4370         gameInfo.outOfBook = NULL;
4371
4372         /* Do we have the ratings? */
4373         if (strcmp(player1Name, white) == 0 &&
4374             strcmp(player2Name, black) == 0) {
4375             if (appData.debugMode)
4376               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4377                       player1Rating, player2Rating);
4378             gameInfo.whiteRating = player1Rating;
4379             gameInfo.blackRating = player2Rating;
4380         } else if (strcmp(player2Name, white) == 0 &&
4381                    strcmp(player1Name, black) == 0) {
4382             if (appData.debugMode)
4383               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4384                       player2Rating, player1Rating);
4385             gameInfo.whiteRating = player2Rating;
4386             gameInfo.blackRating = player1Rating;
4387         }
4388         player1Name[0] = player2Name[0] = NULLCHAR;
4389
4390         /* Silence shouts if requested */
4391         if (appData.quietPlay &&
4392             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4393             SendToICS(ics_prefix);
4394             SendToICS("set shout 0\n");
4395         }
4396     }
4397
4398     /* Deal with midgame name changes */
4399     if (!newGame) {
4400         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4401             if (gameInfo.white) free(gameInfo.white);
4402             gameInfo.white = StrSave(white);
4403         }
4404         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4405             if (gameInfo.black) free(gameInfo.black);
4406             gameInfo.black = StrSave(black);
4407         }
4408     }
4409
4410     /* Throw away game result if anything actually changes in examine mode */
4411     if (gameMode == IcsExamining && !newGame) {
4412         gameInfo.result = GameUnfinished;
4413         if (gameInfo.resultDetails != NULL) {
4414             free(gameInfo.resultDetails);
4415             gameInfo.resultDetails = NULL;
4416         }
4417     }
4418
4419     /* In pausing && IcsExamining mode, we ignore boards coming
4420        in if they are in a different variation than we are. */
4421     if (pauseExamInvalid) return;
4422     if (pausing && gameMode == IcsExamining) {
4423         if (moveNum <= pauseExamForwardMostMove) {
4424             pauseExamInvalid = TRUE;
4425             forwardMostMove = pauseExamForwardMostMove;
4426             return;
4427         }
4428     }
4429
4430   if (appData.debugMode) {
4431     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4432   }
4433     /* Parse the board */
4434     for (k = 0; k < ranks; k++) {
4435       for (j = 0; j < files; j++)
4436         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4437       if(gameInfo.holdingsWidth > 1) {
4438            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4439            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4440       }
4441     }
4442     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4443       board[5][BOARD_RGHT+1] = WhiteAngel;
4444       board[6][BOARD_RGHT+1] = WhiteMarshall;
4445       board[1][0] = BlackMarshall;
4446       board[2][0] = BlackAngel;
4447       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4448     }
4449     CopyBoard(boards[moveNum], board);
4450     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4451     if (moveNum == 0) {
4452         startedFromSetupPosition =
4453           !CompareBoards(board, initialPosition);
4454         if(startedFromSetupPosition)
4455             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4456     }
4457
4458     /* [HGM] Set castling rights. Take the outermost Rooks,
4459        to make it also work for FRC opening positions. Note that board12
4460        is really defective for later FRC positions, as it has no way to
4461        indicate which Rook can castle if they are on the same side of King.
4462        For the initial position we grant rights to the outermost Rooks,
4463        and remember thos rights, and we then copy them on positions
4464        later in an FRC game. This means WB might not recognize castlings with
4465        Rooks that have moved back to their original position as illegal,
4466        but in ICS mode that is not its job anyway.
4467     */
4468     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4469     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4470
4471         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4472             if(board[0][i] == WhiteRook) j = i;
4473         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4474         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4475             if(board[0][i] == WhiteRook) j = i;
4476         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4477         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4478             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4479         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4480         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4481             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4482         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4483
4484         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4485         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4486         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4487             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4488         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4489             if(board[BOARD_HEIGHT-1][k] == bKing)
4490                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4491         if(gameInfo.variant == VariantTwoKings) {
4492             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4493             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4494             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4495         }
4496     } else { int r;
4497         r = boards[moveNum][CASTLING][0] = initialRights[0];
4498         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4499         r = boards[moveNum][CASTLING][1] = initialRights[1];
4500         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4501         r = boards[moveNum][CASTLING][3] = initialRights[3];
4502         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4503         r = boards[moveNum][CASTLING][4] = initialRights[4];
4504         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4505         /* wildcastle kludge: always assume King has rights */
4506         r = boards[moveNum][CASTLING][2] = initialRights[2];
4507         r = boards[moveNum][CASTLING][5] = initialRights[5];
4508     }
4509     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4510     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4511
4512
4513     if (ics_getting_history == H_GOT_REQ_HEADER ||
4514         ics_getting_history == H_GOT_UNREQ_HEADER) {
4515         /* This was an initial position from a move list, not
4516            the current position */
4517         return;
4518     }
4519
4520     /* Update currentMove and known move number limits */
4521     newMove = newGame || moveNum > forwardMostMove;
4522
4523     if (newGame) {
4524         forwardMostMove = backwardMostMove = currentMove = moveNum;
4525         if (gameMode == IcsExamining && moveNum == 0) {
4526           /* Workaround for ICS limitation: we are not told the wild
4527              type when starting to examine a game.  But if we ask for
4528              the move list, the move list header will tell us */
4529             ics_getting_history = H_REQUESTED;
4530             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4531             SendToICS(str);
4532         }
4533     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4534                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4535 #if ZIPPY
4536         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4537         /* [HGM] applied this also to an engine that is silently watching        */
4538         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4539             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4540             gameInfo.variant == currentlyInitializedVariant) {
4541           takeback = forwardMostMove - moveNum;
4542           for (i = 0; i < takeback; i++) {
4543             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4544             SendToProgram("undo\n", &first);
4545           }
4546         }
4547 #endif
4548
4549         forwardMostMove = moveNum;
4550         if (!pausing || currentMove > forwardMostMove)
4551           currentMove = forwardMostMove;
4552     } else {
4553         /* New part of history that is not contiguous with old part */
4554         if (pausing && gameMode == IcsExamining) {
4555             pauseExamInvalid = TRUE;
4556             forwardMostMove = pauseExamForwardMostMove;
4557             return;
4558         }
4559         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4560 #if ZIPPY
4561             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4562                 // [HGM] when we will receive the move list we now request, it will be
4563                 // fed to the engine from the first move on. So if the engine is not
4564                 // in the initial position now, bring it there.
4565                 InitChessProgram(&first, 0);
4566             }
4567 #endif
4568             ics_getting_history = H_REQUESTED;
4569             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4570             SendToICS(str);
4571         }
4572         forwardMostMove = backwardMostMove = currentMove = moveNum;
4573     }
4574
4575     /* Update the clocks */
4576     if (strchr(elapsed_time, '.')) {
4577       /* Time is in ms */
4578       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4579       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4580     } else {
4581       /* Time is in seconds */
4582       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4583       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4584     }
4585
4586
4587 #if ZIPPY
4588     if (appData.zippyPlay && newGame &&
4589         gameMode != IcsObserving && gameMode != IcsIdle &&
4590         gameMode != IcsExamining)
4591       ZippyFirstBoard(moveNum, basetime, increment);
4592 #endif
4593
4594     /* Put the move on the move list, first converting
4595        to canonical algebraic form. */
4596     if (moveNum > 0) {
4597   if (appData.debugMode) {
4598     if (appData.debugMode) { int f = forwardMostMove;
4599         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4600                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4601                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4602     }
4603     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4604     fprintf(debugFP, "moveNum = %d\n", moveNum);
4605     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4606     setbuf(debugFP, NULL);
4607   }
4608         if (moveNum <= backwardMostMove) {
4609             /* We don't know what the board looked like before
4610                this move.  Punt. */
4611           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4612             strcat(parseList[moveNum - 1], " ");
4613             strcat(parseList[moveNum - 1], elapsed_time);
4614             moveList[moveNum - 1][0] = NULLCHAR;
4615         } else if (strcmp(move_str, "none") == 0) {
4616             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4617             /* Again, we don't know what the board looked like;
4618                this is really the start of the game. */
4619             parseList[moveNum - 1][0] = NULLCHAR;
4620             moveList[moveNum - 1][0] = NULLCHAR;
4621             backwardMostMove = moveNum;
4622             startedFromSetupPosition = TRUE;
4623             fromX = fromY = toX = toY = -1;
4624         } else {
4625           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4626           //                 So we parse the long-algebraic move string in stead of the SAN move
4627           int valid; char buf[MSG_SIZ], *prom;
4628
4629           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4630                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4631           // str looks something like "Q/a1-a2"; kill the slash
4632           if(str[1] == '/')
4633             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4634           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4635           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4636                 strcat(buf, prom); // long move lacks promo specification!
4637           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4638                 if(appData.debugMode)
4639                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4640                 safeStrCpy(move_str, buf, MSG_SIZ);
4641           }
4642           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4643                                 &fromX, &fromY, &toX, &toY, &promoChar)
4644                || ParseOneMove(buf, moveNum - 1, &moveType,
4645                                 &fromX, &fromY, &toX, &toY, &promoChar);
4646           // end of long SAN patch
4647           if (valid) {
4648             (void) CoordsToAlgebraic(boards[moveNum - 1],
4649                                      PosFlags(moveNum - 1),
4650                                      fromY, fromX, toY, toX, promoChar,
4651                                      parseList[moveNum-1]);
4652             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4653               case MT_NONE:
4654               case MT_STALEMATE:
4655               default:
4656                 break;
4657               case MT_CHECK:
4658                 if(gameInfo.variant != VariantShogi)
4659                     strcat(parseList[moveNum - 1], "+");
4660                 break;
4661               case MT_CHECKMATE:
4662               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4663                 strcat(parseList[moveNum - 1], "#");
4664                 break;
4665             }
4666             strcat(parseList[moveNum - 1], " ");
4667             strcat(parseList[moveNum - 1], elapsed_time);
4668             /* currentMoveString is set as a side-effect of ParseOneMove */
4669             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4670             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4671             strcat(moveList[moveNum - 1], "\n");
4672
4673             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4674                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4675               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4676                 ChessSquare old, new = boards[moveNum][k][j];
4677                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4678                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4679                   if(old == new) continue;
4680                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4681                   else if(new == WhiteWazir || new == BlackWazir) {
4682                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4683                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4684                       else boards[moveNum][k][j] = old; // preserve type of Gold
4685                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4686                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4687               }
4688           } else {
4689             /* Move from ICS was illegal!?  Punt. */
4690             if (appData.debugMode) {
4691               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4692               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4693             }
4694             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4695             strcat(parseList[moveNum - 1], " ");
4696             strcat(parseList[moveNum - 1], elapsed_time);
4697             moveList[moveNum - 1][0] = NULLCHAR;
4698             fromX = fromY = toX = toY = -1;
4699           }
4700         }
4701   if (appData.debugMode) {
4702     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4703     setbuf(debugFP, NULL);
4704   }
4705
4706 #if ZIPPY
4707         /* Send move to chess program (BEFORE animating it). */
4708         if (appData.zippyPlay && !newGame && newMove &&
4709            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4710
4711             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4712                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4713                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4714                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4715                             move_str);
4716                     DisplayError(str, 0);
4717                 } else {
4718                     if (first.sendTime) {
4719                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4720                     }
4721                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4722                     if (firstMove && !bookHit) {
4723                         firstMove = FALSE;
4724                         if (first.useColors) {
4725                           SendToProgram(gameMode == IcsPlayingWhite ?
4726                                         "white\ngo\n" :
4727                                         "black\ngo\n", &first);
4728                         } else {
4729                           SendToProgram("go\n", &first);
4730                         }
4731                         first.maybeThinking = TRUE;
4732                     }
4733                 }
4734             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4735               if (moveList[moveNum - 1][0] == NULLCHAR) {
4736                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4737                 DisplayError(str, 0);
4738               } else {
4739                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4740                 SendMoveToProgram(moveNum - 1, &first);
4741               }
4742             }
4743         }
4744 #endif
4745     }
4746
4747     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4748         /* If move comes from a remote source, animate it.  If it
4749            isn't remote, it will have already been animated. */
4750         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4751             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4752         }
4753         if (!pausing && appData.highlightLastMove) {
4754             SetHighlights(fromX, fromY, toX, toY);
4755         }
4756     }
4757
4758     /* Start the clocks */
4759     whiteFlag = blackFlag = FALSE;
4760     appData.clockMode = !(basetime == 0 && increment == 0);
4761     if (ticking == 0) {
4762       ics_clock_paused = TRUE;
4763       StopClocks();
4764     } else if (ticking == 1) {
4765       ics_clock_paused = FALSE;
4766     }
4767     if (gameMode == IcsIdle ||
4768         relation == RELATION_OBSERVING_STATIC ||
4769         relation == RELATION_EXAMINING ||
4770         ics_clock_paused)
4771       DisplayBothClocks();
4772     else
4773       StartClocks();
4774
4775     /* Display opponents and material strengths */
4776     if (gameInfo.variant != VariantBughouse &&
4777         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4778         if (tinyLayout || smallLayout) {
4779             if(gameInfo.variant == VariantNormal)
4780               snprintf(str, MSG_SIZ, "%s(%d) %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) %s(%d) {%d %d w%d}",
4785                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4786                     basetime, increment, (int) gameInfo.variant);
4787         } else {
4788             if(gameInfo.variant == VariantNormal)
4789               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4790                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4791                     basetime, increment);
4792             else
4793               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4794                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4795                     basetime, increment, VariantName(gameInfo.variant));
4796         }
4797         DisplayTitle(str);
4798   if (appData.debugMode) {
4799     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4800   }
4801     }
4802
4803
4804     /* Display the board */
4805     if (!pausing && !appData.noGUI) {
4806
4807       if (appData.premove)
4808           if (!gotPremove ||
4809              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4810              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4811               ClearPremoveHighlights();
4812
4813       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4814         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4815       DrawPosition(j, boards[currentMove]);
4816
4817       DisplayMove(moveNum - 1);
4818       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4819             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4820               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4821         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4822       }
4823     }
4824
4825     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4826 #if ZIPPY
4827     if(bookHit) { // [HGM] book: simulate book reply
4828         static char bookMove[MSG_SIZ]; // a bit generous?
4829
4830         programStats.nodes = programStats.depth = programStats.time =
4831         programStats.score = programStats.got_only_move = 0;
4832         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4833
4834         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4835         strcat(bookMove, bookHit);
4836         HandleMachineMove(bookMove, &first);
4837     }
4838 #endif
4839 }
4840
4841 void
4842 GetMoveListEvent ()
4843 {
4844     char buf[MSG_SIZ];
4845     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4846         ics_getting_history = H_REQUESTED;
4847         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4848         SendToICS(buf);
4849     }
4850 }
4851
4852 void
4853 AnalysisPeriodicEvent (int force)
4854 {
4855     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4856          && !force) || !appData.periodicUpdates)
4857       return;
4858
4859     /* Send . command to Crafty to collect stats */
4860     SendToProgram(".\n", &first);
4861
4862     /* Don't send another until we get a response (this makes
4863        us stop sending to old Crafty's which don't understand
4864        the "." command (sending illegal cmds resets node count & time,
4865        which looks bad)) */
4866     programStats.ok_to_send = 0;
4867 }
4868
4869 void
4870 ics_update_width (int new_width)
4871 {
4872         ics_printf("set width %d\n", new_width);
4873 }
4874
4875 void
4876 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4877 {
4878     char buf[MSG_SIZ];
4879
4880     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4881         // null move in variant where engine does not understand it (for analysis purposes)
4882         SendBoard(cps, moveNum + 1); // send position after move in stead.
4883         return;
4884     }
4885     if (cps->useUsermove) {
4886       SendToProgram("usermove ", cps);
4887     }
4888     if (cps->useSAN) {
4889       char *space;
4890       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4891         int len = space - parseList[moveNum];
4892         memcpy(buf, parseList[moveNum], len);
4893         buf[len++] = '\n';
4894         buf[len] = NULLCHAR;
4895       } else {
4896         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4897       }
4898       SendToProgram(buf, cps);
4899     } else {
4900       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4901         AlphaRank(moveList[moveNum], 4);
4902         SendToProgram(moveList[moveNum], cps);
4903         AlphaRank(moveList[moveNum], 4); // and back
4904       } else
4905       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4906        * the engine. It would be nice to have a better way to identify castle
4907        * moves here. */
4908       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4909                                                                          && cps->useOOCastle) {
4910         int fromX = moveList[moveNum][0] - AAA;
4911         int fromY = moveList[moveNum][1] - ONE;
4912         int toX = moveList[moveNum][2] - AAA;
4913         int toY = moveList[moveNum][3] - ONE;
4914         if((boards[moveNum][fromY][fromX] == WhiteKing
4915             && boards[moveNum][toY][toX] == WhiteRook)
4916            || (boards[moveNum][fromY][fromX] == BlackKing
4917                && boards[moveNum][toY][toX] == BlackRook)) {
4918           if(toX > fromX) SendToProgram("O-O\n", cps);
4919           else SendToProgram("O-O-O\n", cps);
4920         }
4921         else SendToProgram(moveList[moveNum], cps);
4922       } else
4923       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4924         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4925           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4926           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4927                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4928         } else
4929           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4930                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4931         SendToProgram(buf, cps);
4932       }
4933       else SendToProgram(moveList[moveNum], cps);
4934       /* End of additions by Tord */
4935     }
4936
4937     /* [HGM] setting up the opening has brought engine in force mode! */
4938     /*       Send 'go' if we are in a mode where machine should play. */
4939     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4940         (gameMode == TwoMachinesPlay   ||
4941 #if ZIPPY
4942          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4943 #endif
4944          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4945         SendToProgram("go\n", cps);
4946   if (appData.debugMode) {
4947     fprintf(debugFP, "(extra)\n");
4948   }
4949     }
4950     setboardSpoiledMachineBlack = 0;
4951 }
4952
4953 void
4954 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4955 {
4956     char user_move[MSG_SIZ];
4957     char suffix[4];
4958
4959     if(gameInfo.variant == VariantSChess && promoChar) {
4960         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4961         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4962     } else suffix[0] = NULLCHAR;
4963
4964     switch (moveType) {
4965       default:
4966         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4967                 (int)moveType, fromX, fromY, toX, toY);
4968         DisplayError(user_move + strlen("say "), 0);
4969         break;
4970       case WhiteKingSideCastle:
4971       case BlackKingSideCastle:
4972       case WhiteQueenSideCastleWild:
4973       case BlackQueenSideCastleWild:
4974       /* PUSH Fabien */
4975       case WhiteHSideCastleFR:
4976       case BlackHSideCastleFR:
4977       /* POP Fabien */
4978         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4979         break;
4980       case WhiteQueenSideCastle:
4981       case BlackQueenSideCastle:
4982       case WhiteKingSideCastleWild:
4983       case BlackKingSideCastleWild:
4984       /* PUSH Fabien */
4985       case WhiteASideCastleFR:
4986       case BlackASideCastleFR:
4987       /* POP Fabien */
4988         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
4989         break;
4990       case WhiteNonPromotion:
4991       case BlackNonPromotion:
4992         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4993         break;
4994       case WhitePromotion:
4995       case BlackPromotion:
4996         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4997           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4998                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4999                 PieceToChar(WhiteFerz));
5000         else if(gameInfo.variant == VariantGreat)
5001           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5002                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5003                 PieceToChar(WhiteMan));
5004         else
5005           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5006                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5007                 promoChar);
5008         break;
5009       case WhiteDrop:
5010       case BlackDrop:
5011       drop:
5012         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5013                  ToUpper(PieceToChar((ChessSquare) fromX)),
5014                  AAA + toX, ONE + toY);
5015         break;
5016       case IllegalMove:  /* could be a variant we don't quite understand */
5017         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5018       case NormalMove:
5019       case WhiteCapturesEnPassant:
5020       case BlackCapturesEnPassant:
5021         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5022                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5023         break;
5024     }
5025     SendToICS(user_move);
5026     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5027         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5028 }
5029
5030 void
5031 UploadGameEvent ()
5032 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5033     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5034     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5035     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5036       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5037       return;
5038     }
5039     if(gameMode != IcsExamining) { // is this ever not the case?
5040         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5041
5042         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5043           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5044         } else { // on FICS we must first go to general examine mode
5045           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5046         }
5047         if(gameInfo.variant != VariantNormal) {
5048             // try figure out wild number, as xboard names are not always valid on ICS
5049             for(i=1; i<=36; i++) {
5050               snprintf(buf, MSG_SIZ, "wild/%d", i);
5051                 if(StringToVariant(buf) == gameInfo.variant) break;
5052             }
5053             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5054             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5055             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5056         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5057         SendToICS(ics_prefix);
5058         SendToICS(buf);
5059         if(startedFromSetupPosition || backwardMostMove != 0) {
5060           fen = PositionToFEN(backwardMostMove, NULL);
5061           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5062             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5063             SendToICS(buf);
5064           } else { // FICS: everything has to set by separate bsetup commands
5065             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5066             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5067             SendToICS(buf);
5068             if(!WhiteOnMove(backwardMostMove)) {
5069                 SendToICS("bsetup tomove black\n");
5070             }
5071             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5072             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5073             SendToICS(buf);
5074             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5075             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5076             SendToICS(buf);
5077             i = boards[backwardMostMove][EP_STATUS];
5078             if(i >= 0) { // set e.p.
5079               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5080                 SendToICS(buf);
5081             }
5082             bsetup++;
5083           }
5084         }
5085       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5086             SendToICS("bsetup done\n"); // switch to normal examining.
5087     }
5088     for(i = backwardMostMove; i<last; i++) {
5089         char buf[20];
5090         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5091         SendToICS(buf);
5092     }
5093     SendToICS(ics_prefix);
5094     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5095 }
5096
5097 void
5098 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5099 {
5100     if (rf == DROP_RANK) {
5101       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5102       sprintf(move, "%c@%c%c\n",
5103                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5104     } else {
5105         if (promoChar == 'x' || promoChar == NULLCHAR) {
5106           sprintf(move, "%c%c%c%c\n",
5107                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5108         } else {
5109             sprintf(move, "%c%c%c%c%c\n",
5110                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5111         }
5112     }
5113 }
5114
5115 void
5116 ProcessICSInitScript (FILE *f)
5117 {
5118     char buf[MSG_SIZ];
5119
5120     while (fgets(buf, MSG_SIZ, f)) {
5121         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5122     }
5123
5124     fclose(f);
5125 }
5126
5127
5128 static int lastX, lastY, selectFlag, dragging;
5129
5130 void
5131 Sweep (int step)
5132 {
5133     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5134     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5135     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5136     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5137     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5138     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5139     do {
5140         promoSweep -= step;
5141         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5142         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5143         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5144         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5145         if(!step) step = -1;
5146     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5147             appData.testLegality && (promoSweep == king ||
5148             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5149     ChangeDragPiece(promoSweep);
5150 }
5151
5152 int
5153 PromoScroll (int x, int y)
5154 {
5155   int step = 0;
5156
5157   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5158   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5159   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5160   if(!step) return FALSE;
5161   lastX = x; lastY = y;
5162   if((promoSweep < BlackPawn) == flipView) step = -step;
5163   if(step > 0) selectFlag = 1;
5164   if(!selectFlag) Sweep(step);
5165   return FALSE;
5166 }
5167
5168 void
5169 NextPiece (int step)
5170 {
5171     ChessSquare piece = boards[currentMove][toY][toX];
5172     do {
5173         pieceSweep -= step;
5174         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5175         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5176         if(!step) step = -1;
5177     } while(PieceToChar(pieceSweep) == '.');
5178     boards[currentMove][toY][toX] = pieceSweep;
5179     DrawPosition(FALSE, boards[currentMove]);
5180     boards[currentMove][toY][toX] = piece;
5181 }
5182 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5183 void
5184 AlphaRank (char *move, int n)
5185 {
5186 //    char *p = move, c; int x, y;
5187
5188     if (appData.debugMode) {
5189         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5190     }
5191
5192     if(move[1]=='*' &&
5193        move[2]>='0' && move[2]<='9' &&
5194        move[3]>='a' && move[3]<='x'    ) {
5195         move[1] = '@';
5196         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5197         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5198     } else
5199     if(move[0]>='0' && move[0]<='9' &&
5200        move[1]>='a' && move[1]<='x' &&
5201        move[2]>='0' && move[2]<='9' &&
5202        move[3]>='a' && move[3]<='x'    ) {
5203         /* input move, Shogi -> normal */
5204         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5205         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5206         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5207         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5208     } else
5209     if(move[1]=='@' &&
5210        move[3]>='0' && move[3]<='9' &&
5211        move[2]>='a' && move[2]<='x'    ) {
5212         move[1] = '*';
5213         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5214         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5215     } else
5216     if(
5217        move[0]>='a' && move[0]<='x' &&
5218        move[3]>='0' && move[3]<='9' &&
5219        move[2]>='a' && move[2]<='x'    ) {
5220          /* output move, normal -> Shogi */
5221         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5222         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5223         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5224         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5225         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5226     }
5227     if (appData.debugMode) {
5228         fprintf(debugFP, "   out = '%s'\n", move);
5229     }
5230 }
5231
5232 char yy_textstr[8000];
5233
5234 /* Parser for moves from gnuchess, ICS, or user typein box */
5235 Boolean
5236 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5237 {
5238     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5239
5240     switch (*moveType) {
5241       case WhitePromotion:
5242       case BlackPromotion:
5243       case WhiteNonPromotion:
5244       case BlackNonPromotion:
5245       case NormalMove:
5246       case WhiteCapturesEnPassant:
5247       case BlackCapturesEnPassant:
5248       case WhiteKingSideCastle:
5249       case WhiteQueenSideCastle:
5250       case BlackKingSideCastle:
5251       case BlackQueenSideCastle:
5252       case WhiteKingSideCastleWild:
5253       case WhiteQueenSideCastleWild:
5254       case BlackKingSideCastleWild:
5255       case BlackQueenSideCastleWild:
5256       /* Code added by Tord: */
5257       case WhiteHSideCastleFR:
5258       case WhiteASideCastleFR:
5259       case BlackHSideCastleFR:
5260       case BlackASideCastleFR:
5261       /* End of code added by Tord */
5262       case IllegalMove:         /* bug or odd chess variant */
5263         *fromX = currentMoveString[0] - AAA;
5264         *fromY = currentMoveString[1] - ONE;
5265         *toX = currentMoveString[2] - AAA;
5266         *toY = currentMoveString[3] - ONE;
5267         *promoChar = currentMoveString[4];
5268         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5269             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5270     if (appData.debugMode) {
5271         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5272     }
5273             *fromX = *fromY = *toX = *toY = 0;
5274             return FALSE;
5275         }
5276         if (appData.testLegality) {
5277           return (*moveType != IllegalMove);
5278         } else {
5279           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5280                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5281         }
5282
5283       case WhiteDrop:
5284       case BlackDrop:
5285         *fromX = *moveType == WhiteDrop ?
5286           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5287           (int) CharToPiece(ToLower(currentMoveString[0]));
5288         *fromY = DROP_RANK;
5289         *toX = currentMoveString[2] - AAA;
5290         *toY = currentMoveString[3] - ONE;
5291         *promoChar = NULLCHAR;
5292         return TRUE;
5293
5294       case AmbiguousMove:
5295       case ImpossibleMove:
5296       case EndOfFile:
5297       case ElapsedTime:
5298       case Comment:
5299       case PGNTag:
5300       case NAG:
5301       case WhiteWins:
5302       case BlackWins:
5303       case GameIsDrawn:
5304       default:
5305     if (appData.debugMode) {
5306         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5307     }
5308         /* bug? */
5309         *fromX = *fromY = *toX = *toY = 0;
5310         *promoChar = NULLCHAR;
5311         return FALSE;
5312     }
5313 }
5314
5315 Boolean pushed = FALSE;
5316 char *lastParseAttempt;
5317
5318 void
5319 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5320 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5321   int fromX, fromY, toX, toY; char promoChar;
5322   ChessMove moveType;
5323   Boolean valid;
5324   int nr = 0;
5325
5326   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5327     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5328     pushed = TRUE;
5329   }
5330   endPV = forwardMostMove;
5331   do {
5332     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5333     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5334     lastParseAttempt = pv;
5335     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5336     if(!valid && nr == 0 &&
5337        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5338         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5339         // Hande case where played move is different from leading PV move
5340         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5341         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5342         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5343         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5344           endPV += 2; // if position different, keep this
5345           moveList[endPV-1][0] = fromX + AAA;
5346           moveList[endPV-1][1] = fromY + ONE;
5347           moveList[endPV-1][2] = toX + AAA;
5348           moveList[endPV-1][3] = toY + ONE;
5349           parseList[endPV-1][0] = NULLCHAR;
5350           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5351         }
5352       }
5353     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5354     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5355     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5356     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5357         valid++; // allow comments in PV
5358         continue;
5359     }
5360     nr++;
5361     if(endPV+1 > framePtr) break; // no space, truncate
5362     if(!valid) break;
5363     endPV++;
5364     CopyBoard(boards[endPV], boards[endPV-1]);
5365     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5366     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5367     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5368     CoordsToAlgebraic(boards[endPV - 1],
5369                              PosFlags(endPV - 1),
5370                              fromY, fromX, toY, toX, promoChar,
5371                              parseList[endPV - 1]);
5372   } while(valid);
5373   if(atEnd == 2) return; // used hidden, for PV conversion
5374   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5375   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5376   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5377                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5378   DrawPosition(TRUE, boards[currentMove]);
5379 }
5380
5381 int
5382 MultiPV (ChessProgramState *cps)
5383 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5384         int i;
5385         for(i=0; i<cps->nrOptions; i++)
5386             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5387                 return i;
5388         return -1;
5389 }
5390
5391 Boolean
5392 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5393 {
5394         int startPV, multi, lineStart, origIndex = index;
5395         char *p, buf2[MSG_SIZ];
5396
5397         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5398         lastX = x; lastY = y;
5399         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5400         lineStart = startPV = index;
5401         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5402         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5403         index = startPV;
5404         do{ while(buf[index] && buf[index] != '\n') index++;
5405         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5406         buf[index] = 0;
5407         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5408                 int n = first.option[multi].value;
5409                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5410                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5411                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5412                 first.option[multi].value = n;
5413                 *start = *end = 0;
5414                 return FALSE;
5415         }
5416         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5417         *start = startPV; *end = index-1;
5418         return TRUE;
5419 }
5420
5421 char *
5422 PvToSAN (char *pv)
5423 {
5424         static char buf[10*MSG_SIZ];
5425         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5426         *buf = NULLCHAR;
5427         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5428         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5429         for(i = forwardMostMove; i<endPV; i++){
5430             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5431             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5432             k += strlen(buf+k);
5433         }
5434         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5435         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5436         endPV = savedEnd;
5437         return buf;
5438 }
5439
5440 Boolean
5441 LoadPV (int x, int y)
5442 { // called on right mouse click to load PV
5443   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5444   lastX = x; lastY = y;
5445   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5446   return TRUE;
5447 }
5448
5449 void
5450 UnLoadPV ()
5451 {
5452   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5453   if(endPV < 0) return;
5454   endPV = -1;
5455   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5456         Boolean saveAnimate = appData.animate;
5457         if(pushed) {
5458             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5459                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5460             } else storedGames--; // abandon shelved tail of original game
5461         }
5462         pushed = FALSE;
5463         forwardMostMove = currentMove;
5464         currentMove = oldFMM;
5465         appData.animate = FALSE;
5466         ToNrEvent(forwardMostMove);
5467         appData.animate = saveAnimate;
5468   }
5469   currentMove = forwardMostMove;
5470   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5471   ClearPremoveHighlights();
5472   DrawPosition(TRUE, boards[currentMove]);
5473 }
5474
5475 void
5476 MovePV (int x, int y, int h)
5477 { // step through PV based on mouse coordinates (called on mouse move)
5478   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5479
5480   // we must somehow check if right button is still down (might be released off board!)
5481   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5482   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5483   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5484   if(!step) return;
5485   lastX = x; lastY = y;
5486
5487   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5488   if(endPV < 0) return;
5489   if(y < margin) step = 1; else
5490   if(y > h - margin) step = -1;
5491   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5492   currentMove += step;
5493   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5494   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5495                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5496   DrawPosition(FALSE, boards[currentMove]);
5497 }
5498
5499
5500 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5501 // All positions will have equal probability, but the current method will not provide a unique
5502 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5503 #define DARK 1
5504 #define LITE 2
5505 #define ANY 3
5506
5507 int squaresLeft[4];
5508 int piecesLeft[(int)BlackPawn];
5509 int seed, nrOfShuffles;
5510
5511 void
5512 GetPositionNumber ()
5513 {       // sets global variable seed
5514         int i;
5515
5516         seed = appData.defaultFrcPosition;
5517         if(seed < 0) { // randomize based on time for negative FRC position numbers
5518                 for(i=0; i<50; i++) seed += random();
5519                 seed = random() ^ random() >> 8 ^ random() << 8;
5520                 if(seed<0) seed = -seed;
5521         }
5522 }
5523
5524 int
5525 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
5544 AddOnePiece (Board board, int pieceType, int rank, int shade)
5545 // calculate where the next piece goes, (any empty square), and put it there
5546 {
5547         int i;
5548
5549         i = seed % squaresLeft[shade];
5550         nrOfShuffles *= squaresLeft[shade];
5551         seed /= squaresLeft[shade];
5552         put(board, pieceType, rank, i, shade);
5553 }
5554
5555 void
5556 AddTwoPieces (Board board, int pieceType, int rank)
5557 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5558 {
5559         int i, n=squaresLeft[ANY], j=n-1, k;
5560
5561         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5562         i = seed % k;  // pick one
5563         nrOfShuffles *= k;
5564         seed /= k;
5565         while(i >= j) i -= j--;
5566         j = n - 1 - j; i += j;
5567         put(board, pieceType, rank, j, ANY);
5568         put(board, pieceType, rank, i, ANY);
5569 }
5570
5571 void
5572 SetUpShuffle (Board board, int number)
5573 {
5574         int i, p, first=1;
5575
5576         GetPositionNumber(); nrOfShuffles = 1;
5577
5578         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5579         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5580         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5581
5582         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5583
5584         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5585             p = (int) board[0][i];
5586             if(p < (int) BlackPawn) piecesLeft[p] ++;
5587             board[0][i] = EmptySquare;
5588         }
5589
5590         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5591             // shuffles restricted to allow normal castling put KRR first
5592             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5593                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5594             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5595                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5596             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5597                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5598             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5599                 put(board, WhiteRook, 0, 0, ANY);
5600             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5601         }
5602
5603         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5604             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5605             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5606                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5607                 while(piecesLeft[p] >= 2) {
5608                     AddOnePiece(board, p, 0, LITE);
5609                     AddOnePiece(board, p, 0, DARK);
5610                 }
5611                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5612             }
5613
5614         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5615             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5616             // but we leave King and Rooks for last, to possibly obey FRC restriction
5617             if(p == (int)WhiteRook) continue;
5618             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5619             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5620         }
5621
5622         // now everything is placed, except perhaps King (Unicorn) and Rooks
5623
5624         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5625             // Last King gets castling rights
5626             while(piecesLeft[(int)WhiteUnicorn]) {
5627                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5628                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5629             }
5630
5631             while(piecesLeft[(int)WhiteKing]) {
5632                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5633                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5634             }
5635
5636
5637         } else {
5638             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5639             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5640         }
5641
5642         // Only Rooks can be left; simply place them all
5643         while(piecesLeft[(int)WhiteRook]) {
5644                 i = put(board, WhiteRook, 0, 0, ANY);
5645                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5646                         if(first) {
5647                                 first=0;
5648                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5649                         }
5650                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5651                 }
5652         }
5653         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5654             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5655         }
5656
5657         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5658 }
5659
5660 int
5661 SetCharTable (char *table, const char * map)
5662 /* [HGM] moved here from winboard.c because of its general usefulness */
5663 /*       Basically a safe strcpy that uses the last character as King */
5664 {
5665     int result = FALSE; int NrPieces;
5666
5667     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5668                     && NrPieces >= 12 && !(NrPieces&1)) {
5669         int i; /* [HGM] Accept even length from 12 to 34 */
5670
5671         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5672         for( i=0; i<NrPieces/2-1; i++ ) {
5673             table[i] = map[i];
5674             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5675         }
5676         table[(int) WhiteKing]  = map[NrPieces/2-1];
5677         table[(int) BlackKing]  = map[NrPieces-1];
5678
5679         result = TRUE;
5680     }
5681
5682     return result;
5683 }
5684
5685 void
5686 Prelude (Board board)
5687 {       // [HGM] superchess: random selection of exo-pieces
5688         int i, j, k; ChessSquare p;
5689         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5690
5691         GetPositionNumber(); // use FRC position number
5692
5693         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5694             SetCharTable(pieceToChar, appData.pieceToCharTable);
5695             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5696                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5697         }
5698
5699         j = seed%4;                 seed /= 4;
5700         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5701         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5702         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5703         j = seed%3 + (seed%3 >= j); seed /= 3;
5704         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5705         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5706         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5707         j = seed%3;                 seed /= 3;
5708         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5709         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5710         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5711         j = seed%2 + (seed%2 >= j); seed /= 2;
5712         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5713         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5714         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5715         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5716         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5717         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5718         put(board, exoPieces[0],    0, 0, ANY);
5719         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5720 }
5721
5722 void
5723 InitPosition (int redraw)
5724 {
5725     ChessSquare (* pieces)[BOARD_FILES];
5726     int i, j, pawnRow, overrule,
5727     oldx = gameInfo.boardWidth,
5728     oldy = gameInfo.boardHeight,
5729     oldh = gameInfo.holdingsWidth;
5730     static int oldv;
5731
5732     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5733
5734     /* [AS] Initialize pv info list [HGM] and game status */
5735     {
5736         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5737             pvInfoList[i].depth = 0;
5738             boards[i][EP_STATUS] = EP_NONE;
5739             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5740         }
5741
5742         initialRulePlies = 0; /* 50-move counter start */
5743
5744         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5745         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5746     }
5747
5748
5749     /* [HGM] logic here is completely changed. In stead of full positions */
5750     /* the initialized data only consist of the two backranks. The switch */
5751     /* selects which one we will use, which is than copied to the Board   */
5752     /* initialPosition, which for the rest is initialized by Pawns and    */
5753     /* empty squares. This initial position is then copied to boards[0],  */
5754     /* possibly after shuffling, so that it remains available.            */
5755
5756     gameInfo.holdingsWidth = 0; /* default board sizes */
5757     gameInfo.boardWidth    = 8;
5758     gameInfo.boardHeight   = 8;
5759     gameInfo.holdingsSize  = 0;
5760     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5761     for(i=0; i<BOARD_FILES-2; i++)
5762       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5763     initialPosition[EP_STATUS] = EP_NONE;
5764     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5765     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5766          SetCharTable(pieceNickName, appData.pieceNickNames);
5767     else SetCharTable(pieceNickName, "............");
5768     pieces = FIDEArray;
5769
5770     switch (gameInfo.variant) {
5771     case VariantFischeRandom:
5772       shuffleOpenings = TRUE;
5773     default:
5774       break;
5775     case VariantShatranj:
5776       pieces = ShatranjArray;
5777       nrCastlingRights = 0;
5778       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5779       break;
5780     case VariantMakruk:
5781       pieces = makrukArray;
5782       nrCastlingRights = 0;
5783       startedFromSetupPosition = TRUE;
5784       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5785       break;
5786     case VariantTwoKings:
5787       pieces = twoKingsArray;
5788       break;
5789     case VariantGrand:
5790       pieces = GrandArray;
5791       nrCastlingRights = 0;
5792       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5793       gameInfo.boardWidth = 10;
5794       gameInfo.boardHeight = 10;
5795       gameInfo.holdingsSize = 7;
5796       break;
5797     case VariantCapaRandom:
5798       shuffleOpenings = TRUE;
5799     case VariantCapablanca:
5800       pieces = CapablancaArray;
5801       gameInfo.boardWidth = 10;
5802       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5803       break;
5804     case VariantGothic:
5805       pieces = GothicArray;
5806       gameInfo.boardWidth = 10;
5807       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5808       break;
5809     case VariantSChess:
5810       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5811       gameInfo.holdingsSize = 7;
5812       break;
5813     case VariantJanus:
5814       pieces = JanusArray;
5815       gameInfo.boardWidth = 10;
5816       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5817       nrCastlingRights = 6;
5818         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5819         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5820         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5821         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5822         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5823         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5824       break;
5825     case VariantFalcon:
5826       pieces = FalconArray;
5827       gameInfo.boardWidth = 10;
5828       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5829       break;
5830     case VariantXiangqi:
5831       pieces = XiangqiArray;
5832       gameInfo.boardWidth  = 9;
5833       gameInfo.boardHeight = 10;
5834       nrCastlingRights = 0;
5835       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5836       break;
5837     case VariantShogi:
5838       pieces = ShogiArray;
5839       gameInfo.boardWidth  = 9;
5840       gameInfo.boardHeight = 9;
5841       gameInfo.holdingsSize = 7;
5842       nrCastlingRights = 0;
5843       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5844       break;
5845     case VariantCourier:
5846       pieces = CourierArray;
5847       gameInfo.boardWidth  = 12;
5848       nrCastlingRights = 0;
5849       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5850       break;
5851     case VariantKnightmate:
5852       pieces = KnightmateArray;
5853       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5854       break;
5855     case VariantSpartan:
5856       pieces = SpartanArray;
5857       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5858       break;
5859     case VariantFairy:
5860       pieces = fairyArray;
5861       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5862       break;
5863     case VariantGreat:
5864       pieces = GreatArray;
5865       gameInfo.boardWidth = 10;
5866       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5867       gameInfo.holdingsSize = 8;
5868       break;
5869     case VariantSuper:
5870       pieces = FIDEArray;
5871       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5872       gameInfo.holdingsSize = 8;
5873       startedFromSetupPosition = TRUE;
5874       break;
5875     case VariantCrazyhouse:
5876     case VariantBughouse:
5877       pieces = FIDEArray;
5878       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5879       gameInfo.holdingsSize = 5;
5880       break;
5881     case VariantWildCastle:
5882       pieces = FIDEArray;
5883       /* !!?shuffle with kings guaranteed to be on d or e file */
5884       shuffleOpenings = 1;
5885       break;
5886     case VariantNoCastle:
5887       pieces = FIDEArray;
5888       nrCastlingRights = 0;
5889       /* !!?unconstrained back-rank shuffle */
5890       shuffleOpenings = 1;
5891       break;
5892     }
5893
5894     overrule = 0;
5895     if(appData.NrFiles >= 0) {
5896         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5897         gameInfo.boardWidth = appData.NrFiles;
5898     }
5899     if(appData.NrRanks >= 0) {
5900         gameInfo.boardHeight = appData.NrRanks;
5901     }
5902     if(appData.holdingsSize >= 0) {
5903         i = appData.holdingsSize;
5904         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5905         gameInfo.holdingsSize = i;
5906     }
5907     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5908     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5909         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5910
5911     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5912     if(pawnRow < 1) pawnRow = 1;
5913     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5914
5915     /* User pieceToChar list overrules defaults */
5916     if(appData.pieceToCharTable != NULL)
5917         SetCharTable(pieceToChar, appData.pieceToCharTable);
5918
5919     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5920
5921         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5922             s = (ChessSquare) 0; /* account holding counts in guard band */
5923         for( i=0; i<BOARD_HEIGHT; i++ )
5924             initialPosition[i][j] = s;
5925
5926         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5927         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5928         initialPosition[pawnRow][j] = WhitePawn;
5929         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5930         if(gameInfo.variant == VariantXiangqi) {
5931             if(j&1) {
5932                 initialPosition[pawnRow][j] =
5933                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5934                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5935                    initialPosition[2][j] = WhiteCannon;
5936                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5937                 }
5938             }
5939         }
5940         if(gameInfo.variant == VariantGrand) {
5941             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5942                initialPosition[0][j] = WhiteRook;
5943                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5944             }
5945         }
5946         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5947     }
5948     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5949
5950             j=BOARD_LEFT+1;
5951             initialPosition[1][j] = WhiteBishop;
5952             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5953             j=BOARD_RGHT-2;
5954             initialPosition[1][j] = WhiteRook;
5955             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5956     }
5957
5958     if( nrCastlingRights == -1) {
5959         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5960         /*       This sets default castling rights from none to normal corners   */
5961         /* Variants with other castling rights must set them themselves above    */
5962         nrCastlingRights = 6;
5963
5964         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5965         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5966         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5967         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5968         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5969         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5970      }
5971
5972      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5973      if(gameInfo.variant == VariantGreat) { // promotion commoners
5974         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5975         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5976         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5977         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5978      }
5979      if( gameInfo.variant == VariantSChess ) {
5980       initialPosition[1][0] = BlackMarshall;
5981       initialPosition[2][0] = BlackAngel;
5982       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5983       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5984       initialPosition[1][1] = initialPosition[2][1] = 
5985       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5986      }
5987   if (appData.debugMode) {
5988     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5989   }
5990     if(shuffleOpenings) {
5991         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5992         startedFromSetupPosition = TRUE;
5993     }
5994     if(startedFromPositionFile) {
5995       /* [HGM] loadPos: use PositionFile for every new game */
5996       CopyBoard(initialPosition, filePosition);
5997       for(i=0; i<nrCastlingRights; i++)
5998           initialRights[i] = filePosition[CASTLING][i];
5999       startedFromSetupPosition = TRUE;
6000     }
6001
6002     CopyBoard(boards[0], initialPosition);
6003
6004     if(oldx != gameInfo.boardWidth ||
6005        oldy != gameInfo.boardHeight ||
6006        oldv != gameInfo.variant ||
6007        oldh != gameInfo.holdingsWidth
6008                                          )
6009             InitDrawingSizes(-2 ,0);
6010
6011     oldv = gameInfo.variant;
6012     if (redraw)
6013       DrawPosition(TRUE, boards[currentMove]);
6014 }
6015
6016 void
6017 SendBoard (ChessProgramState *cps, int moveNum)
6018 {
6019     char message[MSG_SIZ];
6020
6021     if (cps->useSetboard) {
6022       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6023       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6024       SendToProgram(message, cps);
6025       free(fen);
6026
6027     } else {
6028       ChessSquare *bp;
6029       int i, j, left=0, right=BOARD_WIDTH;
6030       /* Kludge to set black to move, avoiding the troublesome and now
6031        * deprecated "black" command.
6032        */
6033       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6034         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6035
6036       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6037
6038       SendToProgram("edit\n", cps);
6039       SendToProgram("#\n", cps);
6040       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6041         bp = &boards[moveNum][i][left];
6042         for (j = left; j < right; j++, bp++) {
6043           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6044           if ((int) *bp < (int) BlackPawn) {
6045             if(j == BOARD_RGHT+1)
6046                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6047             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6048             if(message[0] == '+' || message[0] == '~') {
6049               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6050                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6051                         AAA + j, ONE + i);
6052             }
6053             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6054                 message[1] = BOARD_RGHT   - 1 - j + '1';
6055                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6056             }
6057             SendToProgram(message, cps);
6058           }
6059         }
6060       }
6061
6062       SendToProgram("c\n", cps);
6063       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6064         bp = &boards[moveNum][i][left];
6065         for (j = left; j < right; j++, bp++) {
6066           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6067           if (((int) *bp != (int) EmptySquare)
6068               && ((int) *bp >= (int) BlackPawn)) {
6069             if(j == BOARD_LEFT-2)
6070                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6071             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6072                     AAA + j, ONE + i);
6073             if(message[0] == '+' || message[0] == '~') {
6074               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6075                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6076                         AAA + j, ONE + i);
6077             }
6078             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6079                 message[1] = BOARD_RGHT   - 1 - j + '1';
6080                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6081             }
6082             SendToProgram(message, cps);
6083           }
6084         }
6085       }
6086
6087       SendToProgram(".\n", cps);
6088     }
6089     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6090 }
6091
6092 ChessSquare
6093 DefaultPromoChoice (int white)
6094 {
6095     ChessSquare result;
6096     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6097         result = WhiteFerz; // no choice
6098     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6099         result= WhiteKing; // in Suicide Q is the last thing we want
6100     else if(gameInfo.variant == VariantSpartan)
6101         result = white ? WhiteQueen : WhiteAngel;
6102     else result = WhiteQueen;
6103     if(!white) result = WHITE_TO_BLACK result;
6104     return result;
6105 }
6106
6107 static int autoQueen; // [HGM] oneclick
6108
6109 int
6110 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6111 {
6112     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6113     /* [HGM] add Shogi promotions */
6114     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6115     ChessSquare piece;
6116     ChessMove moveType;
6117     Boolean premove;
6118
6119     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6120     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6121
6122     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6123       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6124         return FALSE;
6125
6126     piece = boards[currentMove][fromY][fromX];
6127     if(gameInfo.variant == VariantShogi) {
6128         promotionZoneSize = BOARD_HEIGHT/3;
6129         highestPromotingPiece = (int)WhiteFerz;
6130     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6131         promotionZoneSize = 3;
6132     }
6133
6134     // Treat Lance as Pawn when it is not representing Amazon
6135     if(gameInfo.variant != VariantSuper) {
6136         if(piece == WhiteLance) piece = WhitePawn; else
6137         if(piece == BlackLance) piece = BlackPawn;
6138     }
6139
6140     // next weed out all moves that do not touch the promotion zone at all
6141     if((int)piece >= BlackPawn) {
6142         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6143              return FALSE;
6144         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6145     } else {
6146         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6147            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6148     }
6149
6150     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6151
6152     // weed out mandatory Shogi promotions
6153     if(gameInfo.variant == VariantShogi) {
6154         if(piece >= BlackPawn) {
6155             if(toY == 0 && piece == BlackPawn ||
6156                toY == 0 && piece == BlackQueen ||
6157                toY <= 1 && piece == BlackKnight) {
6158                 *promoChoice = '+';
6159                 return FALSE;
6160             }
6161         } else {
6162             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6163                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6164                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6165                 *promoChoice = '+';
6166                 return FALSE;
6167             }
6168         }
6169     }
6170
6171     // weed out obviously illegal Pawn moves
6172     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6173         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6174         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6175         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6176         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6177         // note we are not allowed to test for valid (non-)capture, due to premove
6178     }
6179
6180     // we either have a choice what to promote to, or (in Shogi) whether to promote
6181     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6182         *promoChoice = PieceToChar(BlackFerz);  // no choice
6183         return FALSE;
6184     }
6185     // no sense asking what we must promote to if it is going to explode...
6186     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6187         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6188         return FALSE;
6189     }
6190     // give caller the default choice even if we will not make it
6191     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6192     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6193     if(        sweepSelect && gameInfo.variant != VariantGreat
6194                            && gameInfo.variant != VariantGrand
6195                            && gameInfo.variant != VariantSuper) return FALSE;
6196     if(autoQueen) return FALSE; // predetermined
6197
6198     // suppress promotion popup on illegal moves that are not premoves
6199     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6200               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6201     if(appData.testLegality && !premove) {
6202         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6203                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6204         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6205             return FALSE;
6206     }
6207
6208     return TRUE;
6209 }
6210
6211 int
6212 InPalace (int row, int column)
6213 {   /* [HGM] for Xiangqi */
6214     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6215          column < (BOARD_WIDTH + 4)/2 &&
6216          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6217     return FALSE;
6218 }
6219
6220 int
6221 PieceForSquare (int x, int y)
6222 {
6223   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6224      return -1;
6225   else
6226      return boards[currentMove][y][x];
6227 }
6228
6229 int
6230 OKToStartUserMove (int x, int y)
6231 {
6232     ChessSquare from_piece;
6233     int white_piece;
6234
6235     if (matchMode) return FALSE;
6236     if (gameMode == EditPosition) return TRUE;
6237
6238     if (x >= 0 && y >= 0)
6239       from_piece = boards[currentMove][y][x];
6240     else
6241       from_piece = EmptySquare;
6242
6243     if (from_piece == EmptySquare) return FALSE;
6244
6245     white_piece = (int)from_piece >= (int)WhitePawn &&
6246       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6247
6248     switch (gameMode) {
6249       case AnalyzeFile:
6250       case TwoMachinesPlay:
6251       case EndOfGame:
6252         return FALSE;
6253
6254       case IcsObserving:
6255       case IcsIdle:
6256         return FALSE;
6257
6258       case MachinePlaysWhite:
6259       case IcsPlayingBlack:
6260         if (appData.zippyPlay) return FALSE;
6261         if (white_piece) {
6262             DisplayMoveError(_("You are playing Black"));
6263             return FALSE;
6264         }
6265         break;
6266
6267       case MachinePlaysBlack:
6268       case IcsPlayingWhite:
6269         if (appData.zippyPlay) return FALSE;
6270         if (!white_piece) {
6271             DisplayMoveError(_("You are playing White"));
6272             return FALSE;
6273         }
6274         break;
6275
6276       case PlayFromGameFile:
6277             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6278       case EditGame:
6279         if (!white_piece && WhiteOnMove(currentMove)) {
6280             DisplayMoveError(_("It is White's turn"));
6281             return FALSE;
6282         }
6283         if (white_piece && !WhiteOnMove(currentMove)) {
6284             DisplayMoveError(_("It is Black's turn"));
6285             return FALSE;
6286         }
6287         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6288             /* Editing correspondence game history */
6289             /* Could disallow this or prompt for confirmation */
6290             cmailOldMove = -1;
6291         }
6292         break;
6293
6294       case BeginningOfGame:
6295         if (appData.icsActive) return FALSE;
6296         if (!appData.noChessProgram) {
6297             if (!white_piece) {
6298                 DisplayMoveError(_("You are playing White"));
6299                 return FALSE;
6300             }
6301         }
6302         break;
6303
6304       case Training:
6305         if (!white_piece && WhiteOnMove(currentMove)) {
6306             DisplayMoveError(_("It is White's turn"));
6307             return FALSE;
6308         }
6309         if (white_piece && !WhiteOnMove(currentMove)) {
6310             DisplayMoveError(_("It is Black's turn"));
6311             return FALSE;
6312         }
6313         break;
6314
6315       default:
6316       case IcsExamining:
6317         break;
6318     }
6319     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6320         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6321         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6322         && gameMode != AnalyzeFile && gameMode != Training) {
6323         DisplayMoveError(_("Displayed position is not current"));
6324         return FALSE;
6325     }
6326     return TRUE;
6327 }
6328
6329 Boolean
6330 OnlyMove (int *x, int *y, Boolean captures) 
6331 {
6332     DisambiguateClosure cl;
6333     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6334     switch(gameMode) {
6335       case MachinePlaysBlack:
6336       case IcsPlayingWhite:
6337       case BeginningOfGame:
6338         if(!WhiteOnMove(currentMove)) return FALSE;
6339         break;
6340       case MachinePlaysWhite:
6341       case IcsPlayingBlack:
6342         if(WhiteOnMove(currentMove)) return FALSE;
6343         break;
6344       case EditGame:
6345         break;
6346       default:
6347         return FALSE;
6348     }
6349     cl.pieceIn = EmptySquare;
6350     cl.rfIn = *y;
6351     cl.ffIn = *x;
6352     cl.rtIn = -1;
6353     cl.ftIn = -1;
6354     cl.promoCharIn = NULLCHAR;
6355     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6356     if( cl.kind == NormalMove ||
6357         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6358         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6359         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6360       fromX = cl.ff;
6361       fromY = cl.rf;
6362       *x = cl.ft;
6363       *y = cl.rt;
6364       return TRUE;
6365     }
6366     if(cl.kind != ImpossibleMove) return FALSE;
6367     cl.pieceIn = EmptySquare;
6368     cl.rfIn = -1;
6369     cl.ffIn = -1;
6370     cl.rtIn = *y;
6371     cl.ftIn = *x;
6372     cl.promoCharIn = NULLCHAR;
6373     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6374     if( cl.kind == NormalMove ||
6375         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6376         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6377         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6378       fromX = cl.ff;
6379       fromY = cl.rf;
6380       *x = cl.ft;
6381       *y = cl.rt;
6382       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6383       return TRUE;
6384     }
6385     return FALSE;
6386 }
6387
6388 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6389 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6390 int lastLoadGameUseList = FALSE;
6391 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6392 ChessMove lastLoadGameStart = EndOfFile;
6393
6394 void
6395 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6396 {
6397     ChessMove moveType;
6398     ChessSquare pdown, pup;
6399
6400     /* Check if the user is playing in turn.  This is complicated because we
6401        let the user "pick up" a piece before it is his turn.  So the piece he
6402        tried to pick up may have been captured by the time he puts it down!
6403        Therefore we use the color the user is supposed to be playing in this
6404        test, not the color of the piece that is currently on the starting
6405        square---except in EditGame mode, where the user is playing both
6406        sides; fortunately there the capture race can't happen.  (It can
6407        now happen in IcsExamining mode, but that's just too bad.  The user
6408        will get a somewhat confusing message in that case.)
6409        */
6410
6411     switch (gameMode) {
6412       case AnalyzeFile:
6413       case TwoMachinesPlay:
6414       case EndOfGame:
6415       case IcsObserving:
6416       case IcsIdle:
6417         /* We switched into a game mode where moves are not accepted,
6418            perhaps while the mouse button was down. */
6419         return;
6420
6421       case MachinePlaysWhite:
6422         /* User is moving for Black */
6423         if (WhiteOnMove(currentMove)) {
6424             DisplayMoveError(_("It is White's turn"));
6425             return;
6426         }
6427         break;
6428
6429       case MachinePlaysBlack:
6430         /* User is moving for White */
6431         if (!WhiteOnMove(currentMove)) {
6432             DisplayMoveError(_("It is Black's turn"));
6433             return;
6434         }
6435         break;
6436
6437       case PlayFromGameFile:
6438             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6439       case EditGame:
6440       case IcsExamining:
6441       case BeginningOfGame:
6442       case AnalyzeMode:
6443       case Training:
6444         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6445         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6446             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6447             /* User is moving for Black */
6448             if (WhiteOnMove(currentMove)) {
6449                 DisplayMoveError(_("It is White's turn"));
6450                 return;
6451             }
6452         } else {
6453             /* User is moving for White */
6454             if (!WhiteOnMove(currentMove)) {
6455                 DisplayMoveError(_("It is Black's turn"));
6456                 return;
6457             }
6458         }
6459         break;
6460
6461       case IcsPlayingBlack:
6462         /* User is moving for Black */
6463         if (WhiteOnMove(currentMove)) {
6464             if (!appData.premove) {
6465                 DisplayMoveError(_("It is White's turn"));
6466             } else if (toX >= 0 && toY >= 0) {
6467                 premoveToX = toX;
6468                 premoveToY = toY;
6469                 premoveFromX = fromX;
6470                 premoveFromY = fromY;
6471                 premovePromoChar = promoChar;
6472                 gotPremove = 1;
6473                 if (appData.debugMode)
6474                     fprintf(debugFP, "Got premove: fromX %d,"
6475                             "fromY %d, toX %d, toY %d\n",
6476                             fromX, fromY, toX, toY);
6477             }
6478             return;
6479         }
6480         break;
6481
6482       case IcsPlayingWhite:
6483         /* User is moving for White */
6484         if (!WhiteOnMove(currentMove)) {
6485             if (!appData.premove) {
6486                 DisplayMoveError(_("It is Black's turn"));
6487             } else if (toX >= 0 && toY >= 0) {
6488                 premoveToX = toX;
6489                 premoveToY = toY;
6490                 premoveFromX = fromX;
6491                 premoveFromY = fromY;
6492                 premovePromoChar = promoChar;
6493                 gotPremove = 1;
6494                 if (appData.debugMode)
6495                     fprintf(debugFP, "Got premove: fromX %d,"
6496                             "fromY %d, toX %d, toY %d\n",
6497                             fromX, fromY, toX, toY);
6498             }
6499             return;
6500         }
6501         break;
6502
6503       default:
6504         break;
6505
6506       case EditPosition:
6507         /* EditPosition, empty square, or different color piece;
6508            click-click move is possible */
6509         if (toX == -2 || toY == -2) {
6510             boards[0][fromY][fromX] = EmptySquare;
6511             DrawPosition(FALSE, boards[currentMove]);
6512             return;
6513         } else if (toX >= 0 && toY >= 0) {
6514             boards[0][toY][toX] = boards[0][fromY][fromX];
6515             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6516                 if(boards[0][fromY][0] != EmptySquare) {
6517                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6518                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6519                 }
6520             } else
6521             if(fromX == BOARD_RGHT+1) {
6522                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6523                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6524                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6525                 }
6526             } else
6527             boards[0][fromY][fromX] = EmptySquare;
6528             DrawPosition(FALSE, boards[currentMove]);
6529             return;
6530         }
6531         return;
6532     }
6533
6534     if(toX < 0 || toY < 0) return;
6535     pdown = boards[currentMove][fromY][fromX];
6536     pup = boards[currentMove][toY][toX];
6537
6538     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6539     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6540          if( pup != EmptySquare ) return;
6541          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6542            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6543                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6544            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6545            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6546            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6547            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6548          fromY = DROP_RANK;
6549     }
6550
6551     /* [HGM] always test for legality, to get promotion info */
6552     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6553                                          fromY, fromX, toY, toX, promoChar);
6554
6555     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6556
6557     /* [HGM] but possibly ignore an IllegalMove result */
6558     if (appData.testLegality) {
6559         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6560             DisplayMoveError(_("Illegal move"));
6561             return;
6562         }
6563     }
6564
6565     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6566 }
6567
6568 /* Common tail of UserMoveEvent and DropMenuEvent */
6569 int
6570 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6571 {
6572     char *bookHit = 0;
6573
6574     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6575         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6576         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6577         if(WhiteOnMove(currentMove)) {
6578             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6579         } else {
6580             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6581         }
6582     }
6583
6584     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6585        move type in caller when we know the move is a legal promotion */
6586     if(moveType == NormalMove && promoChar)
6587         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6588
6589     /* [HGM] <popupFix> The following if has been moved here from
6590        UserMoveEvent(). Because it seemed to belong here (why not allow
6591        piece drops in training games?), and because it can only be
6592        performed after it is known to what we promote. */
6593     if (gameMode == Training) {
6594       /* compare the move played on the board to the next move in the
6595        * game. If they match, display the move and the opponent's response.
6596        * If they don't match, display an error message.
6597        */
6598       int saveAnimate;
6599       Board testBoard;
6600       CopyBoard(testBoard, boards[currentMove]);
6601       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6602
6603       if (CompareBoards(testBoard, boards[currentMove+1])) {
6604         ForwardInner(currentMove+1);
6605
6606         /* Autoplay the opponent's response.
6607          * if appData.animate was TRUE when Training mode was entered,
6608          * the response will be animated.
6609          */
6610         saveAnimate = appData.animate;
6611         appData.animate = animateTraining;
6612         ForwardInner(currentMove+1);
6613         appData.animate = saveAnimate;
6614
6615         /* check for the end of the game */
6616         if (currentMove >= forwardMostMove) {
6617           gameMode = PlayFromGameFile;
6618           ModeHighlight();
6619           SetTrainingModeOff();
6620           DisplayInformation(_("End of game"));
6621         }
6622       } else {
6623         DisplayError(_("Incorrect move"), 0);
6624       }
6625       return 1;
6626     }
6627
6628   /* Ok, now we know that the move is good, so we can kill
6629      the previous line in Analysis Mode */
6630   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6631                                 && currentMove < forwardMostMove) {
6632     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6633     else forwardMostMove = currentMove;
6634   }
6635
6636   /* If we need the chess program but it's dead, restart it */
6637   ResurrectChessProgram();
6638
6639   /* A user move restarts a paused game*/
6640   if (pausing)
6641     PauseEvent();
6642
6643   thinkOutput[0] = NULLCHAR;
6644
6645   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6646
6647   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6648     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6649     return 1;
6650   }
6651
6652   if (gameMode == BeginningOfGame) {
6653     if (appData.noChessProgram) {
6654       gameMode = EditGame;
6655       SetGameInfo();
6656     } else {
6657       char buf[MSG_SIZ];
6658       gameMode = MachinePlaysBlack;
6659       StartClocks();
6660       SetGameInfo();
6661       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6662       DisplayTitle(buf);
6663       if (first.sendName) {
6664         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6665         SendToProgram(buf, &first);
6666       }
6667       StartClocks();
6668     }
6669     ModeHighlight();
6670   }
6671
6672   /* Relay move to ICS or chess engine */
6673   if (appData.icsActive) {
6674     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6675         gameMode == IcsExamining) {
6676       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6677         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6678         SendToICS("draw ");
6679         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6680       }
6681       // also send plain move, in case ICS does not understand atomic claims
6682       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6683       ics_user_moved = 1;
6684     }
6685   } else {
6686     if (first.sendTime && (gameMode == BeginningOfGame ||
6687                            gameMode == MachinePlaysWhite ||
6688                            gameMode == MachinePlaysBlack)) {
6689       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6690     }
6691     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6692          // [HGM] book: if program might be playing, let it use book
6693         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6694         first.maybeThinking = TRUE;
6695     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6696         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6697         SendBoard(&first, currentMove+1);
6698     } else SendMoveToProgram(forwardMostMove-1, &first);
6699     if (currentMove == cmailOldMove + 1) {
6700       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6701     }
6702   }
6703
6704   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6705
6706   switch (gameMode) {
6707   case EditGame:
6708     if(appData.testLegality)
6709     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6710     case MT_NONE:
6711     case MT_CHECK:
6712       break;
6713     case MT_CHECKMATE:
6714     case MT_STAINMATE:
6715       if (WhiteOnMove(currentMove)) {
6716         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6717       } else {
6718         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6719       }
6720       break;
6721     case MT_STALEMATE:
6722       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6723       break;
6724     }
6725     break;
6726
6727   case MachinePlaysBlack:
6728   case MachinePlaysWhite:
6729     /* disable certain menu options while machine is thinking */
6730     SetMachineThinkingEnables();
6731     break;
6732
6733   default:
6734     break;
6735   }
6736
6737   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6738   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6739
6740   if(bookHit) { // [HGM] book: simulate book reply
6741         static char bookMove[MSG_SIZ]; // a bit generous?
6742
6743         programStats.nodes = programStats.depth = programStats.time =
6744         programStats.score = programStats.got_only_move = 0;
6745         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6746
6747         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6748         strcat(bookMove, bookHit);
6749         HandleMachineMove(bookMove, &first);
6750   }
6751   return 1;
6752 }
6753
6754 void
6755 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6756 {
6757     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6758     Markers *m = (Markers *) closure;
6759     if(rf == fromY && ff == fromX)
6760         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6761                          || kind == WhiteCapturesEnPassant
6762                          || kind == BlackCapturesEnPassant);
6763     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6764 }
6765
6766 void
6767 MarkTargetSquares (int clear)
6768 {
6769   int x, y;
6770   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6771      !appData.testLegality || gameMode == EditPosition) return;
6772   if(clear) {
6773     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6774   } else {
6775     int capt = 0;
6776     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6777     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6778       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6779       if(capt)
6780       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6781     }
6782   }
6783   DrawPosition(TRUE, NULL);
6784 }
6785
6786 int
6787 Explode (Board board, int fromX, int fromY, int toX, int toY)
6788 {
6789     if(gameInfo.variant == VariantAtomic &&
6790        (board[toY][toX] != EmptySquare ||                     // capture?
6791         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6792                          board[fromY][fromX] == BlackPawn   )
6793       )) {
6794         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6795         return TRUE;
6796     }
6797     return FALSE;
6798 }
6799
6800 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6801
6802 int
6803 CanPromote (ChessSquare piece, int y)
6804 {
6805         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6806         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6807         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6808            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6809            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6810                                                   gameInfo.variant == VariantMakruk) return FALSE;
6811         return (piece == BlackPawn && y == 1 ||
6812                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6813                 piece == BlackLance && y == 1 ||
6814                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6815 }
6816
6817 void
6818 LeftClick (ClickType clickType, int xPix, int yPix)
6819 {
6820     int x, y;
6821     Boolean saveAnimate;
6822     static int second = 0, promotionChoice = 0, clearFlag = 0;
6823     char promoChoice = NULLCHAR;
6824     ChessSquare piece;
6825
6826     if(appData.seekGraph && appData.icsActive && loggedOn &&
6827         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6828         SeekGraphClick(clickType, xPix, yPix, 0);
6829         return;
6830     }
6831
6832     if (clickType == Press) ErrorPopDown();
6833
6834     x = EventToSquare(xPix, BOARD_WIDTH);
6835     y = EventToSquare(yPix, BOARD_HEIGHT);
6836     if (!flipView && y >= 0) {
6837         y = BOARD_HEIGHT - 1 - y;
6838     }
6839     if (flipView && x >= 0) {
6840         x = BOARD_WIDTH - 1 - x;
6841     }
6842
6843     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6844         defaultPromoChoice = promoSweep;
6845         promoSweep = EmptySquare;   // terminate sweep
6846         promoDefaultAltered = TRUE;
6847         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6848     }
6849
6850     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6851         if(clickType == Release) return; // ignore upclick of click-click destination
6852         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6853         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6854         if(gameInfo.holdingsWidth &&
6855                 (WhiteOnMove(currentMove)
6856                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6857                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6858             // click in right holdings, for determining promotion piece
6859             ChessSquare p = boards[currentMove][y][x];
6860             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6861             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6862             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6863                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6864                 fromX = fromY = -1;
6865                 return;
6866             }
6867         }
6868         DrawPosition(FALSE, boards[currentMove]);
6869         return;
6870     }
6871
6872     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6873     if(clickType == Press
6874             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6875               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6876               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6877         return;
6878
6879     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6880         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6881
6882     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6883         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6884                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6885         defaultPromoChoice = DefaultPromoChoice(side);
6886     }
6887
6888     autoQueen = appData.alwaysPromoteToQueen;
6889
6890     if (fromX == -1) {
6891       int originalY = y;
6892       gatingPiece = EmptySquare;
6893       if (clickType != Press) {
6894         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6895             DragPieceEnd(xPix, yPix); dragging = 0;
6896             DrawPosition(FALSE, NULL);
6897         }
6898         return;
6899       }
6900       fromX = x; fromY = y; toX = toY = -1;
6901       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6902          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6903          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6904             /* First square */
6905             if (OKToStartUserMove(fromX, fromY)) {
6906                 second = 0;
6907                 MarkTargetSquares(0);
6908                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6909                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6910                     promoSweep = defaultPromoChoice;
6911                     selectFlag = 0; lastX = xPix; lastY = yPix;
6912                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6913                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6914                 }
6915                 if (appData.highlightDragging) {
6916                     SetHighlights(fromX, fromY, -1, -1);
6917                 }
6918             } else fromX = fromY = -1;
6919             return;
6920         }
6921     }
6922
6923     /* fromX != -1 */
6924     if (clickType == Press && gameMode != EditPosition) {
6925         ChessSquare fromP;
6926         ChessSquare toP;
6927         int frc;
6928
6929         // ignore off-board to clicks
6930         if(y < 0 || x < 0) return;
6931
6932         /* Check if clicking again on the same color piece */
6933         fromP = boards[currentMove][fromY][fromX];
6934         toP = boards[currentMove][y][x];
6935         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6936         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6937              WhitePawn <= toP && toP <= WhiteKing &&
6938              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6939              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6940             (BlackPawn <= fromP && fromP <= BlackKing &&
6941              BlackPawn <= toP && toP <= BlackKing &&
6942              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6943              !(fromP == BlackKing && toP == BlackRook && frc))) {
6944             /* Clicked again on same color piece -- changed his mind */
6945             second = (x == fromX && y == fromY);
6946             promoDefaultAltered = FALSE;
6947             MarkTargetSquares(1);
6948            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6949             if (appData.highlightDragging) {
6950                 SetHighlights(x, y, -1, -1);
6951             } else {
6952                 ClearHighlights();
6953             }
6954             if (OKToStartUserMove(x, y)) {
6955                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6956                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6957                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6958                  gatingPiece = boards[currentMove][fromY][fromX];
6959                 else gatingPiece = EmptySquare;
6960                 fromX = x;
6961                 fromY = y; dragging = 1;
6962                 MarkTargetSquares(0);
6963                 DragPieceBegin(xPix, yPix, FALSE);
6964                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6965                     promoSweep = defaultPromoChoice;
6966                     selectFlag = 0; lastX = xPix; lastY = yPix;
6967                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6968                 }
6969             }
6970            }
6971            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6972            second = FALSE; 
6973         }
6974         // ignore clicks on holdings
6975         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6976     }
6977
6978     if (clickType == Release && x == fromX && y == fromY) {
6979         DragPieceEnd(xPix, yPix); dragging = 0;
6980         if(clearFlag) {
6981             // a deferred attempt to click-click move an empty square on top of a piece
6982             boards[currentMove][y][x] = EmptySquare;
6983             ClearHighlights();
6984             DrawPosition(FALSE, boards[currentMove]);
6985             fromX = fromY = -1; clearFlag = 0;
6986             return;
6987         }
6988         if (appData.animateDragging) {
6989             /* Undo animation damage if any */
6990             DrawPosition(FALSE, NULL);
6991         }
6992         if (second) {
6993             /* Second up/down in same square; just abort move */
6994             second = 0;
6995             fromX = fromY = -1;
6996             gatingPiece = EmptySquare;
6997             ClearHighlights();
6998             gotPremove = 0;
6999             ClearPremoveHighlights();
7000         } else {
7001             /* First upclick in same square; start click-click mode */
7002             SetHighlights(x, y, -1, -1);
7003         }
7004         return;
7005     }
7006
7007     clearFlag = 0;
7008
7009     /* we now have a different from- and (possibly off-board) to-square */
7010     /* Completed move */
7011     toX = x;
7012     toY = y;
7013     saveAnimate = appData.animate;
7014     MarkTargetSquares(1);
7015     if (clickType == Press) {
7016         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7017             // must be Edit Position mode with empty-square selected
7018             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7019             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7020             return;
7021         }
7022         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7023             ChessSquare piece = boards[currentMove][fromY][fromX];
7024             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7025             promoSweep = defaultPromoChoice;
7026             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7027             selectFlag = 0; lastX = xPix; lastY = yPix;
7028             Sweep(0); // Pawn that is going to promote: preview promotion piece
7029             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7030             DrawPosition(FALSE, boards[currentMove]);
7031             return;
7032         }
7033         /* Finish clickclick move */
7034         if (appData.animate || appData.highlightLastMove) {
7035             SetHighlights(fromX, fromY, toX, toY);
7036         } else {
7037             ClearHighlights();
7038         }
7039     } else {
7040         /* Finish drag move */
7041         if (appData.highlightLastMove) {
7042             SetHighlights(fromX, fromY, toX, toY);
7043         } else {
7044             ClearHighlights();
7045         }
7046         DragPieceEnd(xPix, yPix); dragging = 0;
7047         /* Don't animate move and drag both */
7048         appData.animate = FALSE;
7049     }
7050
7051     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7052     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7053         ChessSquare piece = boards[currentMove][fromY][fromX];
7054         if(gameMode == EditPosition && piece != EmptySquare &&
7055            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7056             int n;
7057
7058             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7059                 n = PieceToNumber(piece - (int)BlackPawn);
7060                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7061                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7062                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7063             } else
7064             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7065                 n = PieceToNumber(piece);
7066                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7067                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7068                 boards[currentMove][n][BOARD_WIDTH-2]++;
7069             }
7070             boards[currentMove][fromY][fromX] = EmptySquare;
7071         }
7072         ClearHighlights();
7073         fromX = fromY = -1;
7074         DrawPosition(TRUE, boards[currentMove]);
7075         return;
7076     }
7077
7078     // off-board moves should not be highlighted
7079     if(x < 0 || y < 0) ClearHighlights();
7080
7081     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7082
7083     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7084         SetHighlights(fromX, fromY, toX, toY);
7085         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7086             // [HGM] super: promotion to captured piece selected from holdings
7087             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7088             promotionChoice = TRUE;
7089             // kludge follows to temporarily execute move on display, without promoting yet
7090             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7091             boards[currentMove][toY][toX] = p;
7092             DrawPosition(FALSE, boards[currentMove]);
7093             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7094             boards[currentMove][toY][toX] = q;
7095             DisplayMessage("Click in holdings to choose piece", "");
7096             return;
7097         }
7098         PromotionPopUp();
7099     } else {
7100         int oldMove = currentMove;
7101         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7102         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7103         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7104         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7105            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7106             DrawPosition(TRUE, boards[currentMove]);
7107         fromX = fromY = -1;
7108     }
7109     appData.animate = saveAnimate;
7110     if (appData.animate || appData.animateDragging) {
7111         /* Undo animation damage if needed */
7112         DrawPosition(FALSE, NULL);
7113     }
7114 }
7115
7116 int
7117 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7118 {   // front-end-free part taken out of PieceMenuPopup
7119     int whichMenu; int xSqr, ySqr;
7120
7121     if(seekGraphUp) { // [HGM] seekgraph
7122         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7123         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7124         return -2;
7125     }
7126
7127     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7128          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7129         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7130         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7131         if(action == Press)   {
7132             originalFlip = flipView;
7133             flipView = !flipView; // temporarily flip board to see game from partners perspective
7134             DrawPosition(TRUE, partnerBoard);
7135             DisplayMessage(partnerStatus, "");
7136             partnerUp = TRUE;
7137         } else if(action == Release) {
7138             flipView = originalFlip;
7139             DrawPosition(TRUE, boards[currentMove]);
7140             partnerUp = FALSE;
7141         }
7142         return -2;
7143     }
7144
7145     xSqr = EventToSquare(x, BOARD_WIDTH);
7146     ySqr = EventToSquare(y, BOARD_HEIGHT);
7147     if (action == Release) {
7148         if(pieceSweep != EmptySquare) {
7149             EditPositionMenuEvent(pieceSweep, toX, toY);
7150             pieceSweep = EmptySquare;
7151         } else UnLoadPV(); // [HGM] pv
7152     }
7153     if (action != Press) return -2; // return code to be ignored
7154     switch (gameMode) {
7155       case IcsExamining:
7156         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7157       case EditPosition:
7158         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7159         if (xSqr < 0 || ySqr < 0) return -1;
7160         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7161         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7162         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7163         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7164         NextPiece(0);
7165         return 2; // grab
7166       case IcsObserving:
7167         if(!appData.icsEngineAnalyze) return -1;
7168       case IcsPlayingWhite:
7169       case IcsPlayingBlack:
7170         if(!appData.zippyPlay) goto noZip;
7171       case AnalyzeMode:
7172       case AnalyzeFile:
7173       case MachinePlaysWhite:
7174       case MachinePlaysBlack:
7175       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7176         if (!appData.dropMenu) {
7177           LoadPV(x, y);
7178           return 2; // flag front-end to grab mouse events
7179         }
7180         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7181            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7182       case EditGame:
7183       noZip:
7184         if (xSqr < 0 || ySqr < 0) return -1;
7185         if (!appData.dropMenu || appData.testLegality &&
7186             gameInfo.variant != VariantBughouse &&
7187             gameInfo.variant != VariantCrazyhouse) return -1;
7188         whichMenu = 1; // drop menu
7189         break;
7190       default:
7191         return -1;
7192     }
7193
7194     if (((*fromX = xSqr) < 0) ||
7195         ((*fromY = ySqr) < 0)) {
7196         *fromX = *fromY = -1;
7197         return -1;
7198     }
7199     if (flipView)
7200       *fromX = BOARD_WIDTH - 1 - *fromX;
7201     else
7202       *fromY = BOARD_HEIGHT - 1 - *fromY;
7203
7204     return whichMenu;
7205 }
7206
7207 void
7208 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
7566         // Repetition draws and 50-move rule can be applied independently of legality testing
7567
7568                 /* Check for rep-draws */
7569                 count = 0;
7570                 for(k = forwardMostMove-2;
7571                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7572                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7573                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7574                     k-=2)
7575                 {   int rights=0;
7576                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7577                         /* compare castling rights */
7578                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7579                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7580                                 rights++; /* King lost rights, while rook still had them */
7581                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7582                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7583                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7584                                    rights++; /* but at least one rook lost them */
7585                         }
7586                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7587                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7588                                 rights++;
7589                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7590                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7591                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7592                                    rights++;
7593                         }
7594                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7595                             && appData.drawRepeats > 1) {
7596                              /* adjudicate after user-specified nr of repeats */
7597                              int result = GameIsDrawn;
7598                              char *details = "XBoard adjudication: repetition draw";
7599                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7600                                 // [HGM] xiangqi: check for forbidden perpetuals
7601                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7602                                 for(m=forwardMostMove; m>k; m-=2) {
7603                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7604                                         ourPerpetual = 0; // the current mover did not always check
7605                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7606                                         hisPerpetual = 0; // the opponent did not always check
7607                                 }
7608                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7609                                                                         ourPerpetual, hisPerpetual);
7610                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7611                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7612                                     details = "Xboard adjudication: perpetual checking";
7613                                 } else
7614                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7615                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7616                                 } else
7617                                 // Now check for perpetual chases
7618                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7619                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7620                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7621                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7622                                         static char resdet[MSG_SIZ];
7623                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7624                                         details = resdet;
7625                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7626                                     } else
7627                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7628                                         break; // Abort repetition-checking loop.
7629                                 }
7630                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7631                              }
7632                              if(engineOpponent) {
7633                                SendToProgram("force\n", engineOpponent); // suppress reply
7634                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7635                              }
7636                              GameEnds( result, details, GE_XBOARD );
7637                              return 1;
7638                         }
7639                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7640                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7641                     }
7642                 }
7643
7644                 /* Now we test for 50-move draws. Determine ply count */
7645                 count = forwardMostMove;
7646                 /* look for last irreversble move */
7647                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7648                     count--;
7649                 /* if we hit starting position, add initial plies */
7650                 if( count == backwardMostMove )
7651                     count -= initialRulePlies;
7652                 count = forwardMostMove - count;
7653                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7654                         // adjust reversible move counter for checks in Xiangqi
7655                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7656                         if(i < backwardMostMove) i = backwardMostMove;
7657                         while(i <= forwardMostMove) {
7658                                 lastCheck = inCheck; // check evasion does not count
7659                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7660                                 if(inCheck || lastCheck) count--; // check does not count
7661                                 i++;
7662                         }
7663                 }
7664                 if( count >= 100)
7665                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7666                          /* this is used to judge if draw claims are legal */
7667                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7668                          if(engineOpponent) {
7669                            SendToProgram("force\n", engineOpponent); // suppress reply
7670                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7671                          }
7672                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7673                          return 1;
7674                 }
7675
7676                 /* if draw offer is pending, treat it as a draw claim
7677                  * when draw condition present, to allow engines a way to
7678                  * claim draws before making their move to avoid a race
7679                  * condition occurring after their move
7680                  */
7681                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7682                          char *p = NULL;
7683                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7684                              p = "Draw claim: 50-move rule";
7685                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7686                              p = "Draw claim: 3-fold repetition";
7687                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7688                              p = "Draw claim: insufficient mating material";
7689                          if( p != NULL && canAdjudicate) {
7690                              if(engineOpponent) {
7691                                SendToProgram("force\n", engineOpponent); // suppress reply
7692                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7693                              }
7694                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7695                              return 1;
7696                          }
7697                 }
7698
7699                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7700                     if(engineOpponent) {
7701                       SendToProgram("force\n", engineOpponent); // suppress reply
7702                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7703                     }
7704                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7705                     return 1;
7706                 }
7707         return 0;
7708 }
7709
7710 char *
7711 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7712 {   // [HGM] book: this routine intercepts moves to simulate book replies
7713     char *bookHit = NULL;
7714
7715     //first determine if the incoming move brings opponent into his book
7716     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7717         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7718     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7719     if(bookHit != NULL && !cps->bookSuspend) {
7720         // make sure opponent is not going to reply after receiving move to book position
7721         SendToProgram("force\n", cps);
7722         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7723     }
7724     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7725     // now arrange restart after book miss
7726     if(bookHit) {
7727         // after a book hit we never send 'go', and the code after the call to this routine
7728         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7729         char buf[MSG_SIZ], *move = bookHit;
7730         if(cps->useSAN) {
7731             int fromX, fromY, toX, toY;
7732             char promoChar;
7733             ChessMove moveType;
7734             move = buf + 30;
7735             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7736                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7737                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7738                                     PosFlags(forwardMostMove),
7739                                     fromY, fromX, toY, toX, promoChar, move);
7740             } else {
7741                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7742                 bookHit = NULL;
7743             }
7744         }
7745         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7746         SendToProgram(buf, cps);
7747         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7748     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7749         SendToProgram("go\n", cps);
7750         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7751     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7752         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7753             SendToProgram("go\n", cps);
7754         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7755     }
7756     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7757 }
7758
7759 char *savedMessage;
7760 ChessProgramState *savedState;
7761 void
7762 DeferredBookMove (void)
7763 {
7764         if(savedState->lastPing != savedState->lastPong)
7765                     ScheduleDelayedEvent(DeferredBookMove, 10);
7766         else
7767         HandleMachineMove(savedMessage, savedState);
7768 }
7769
7770 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7771
7772 void
7773 HandleMachineMove (char *message, ChessProgramState *cps)
7774 {
7775     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7776     char realname[MSG_SIZ];
7777     int fromX, fromY, toX, toY;
7778     ChessMove moveType;
7779     char promoChar;
7780     char *p, *pv=buf1;
7781     int machineWhite;
7782     char *bookHit;
7783
7784     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7785         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7786         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7787             DisplayError(_("Invalid pairing from pairing engine"), 0);
7788             return;
7789         }
7790         pairingReceived = 1;
7791         NextMatchGame();
7792         return; // Skim the pairing messages here.
7793     }
7794
7795     cps->userError = 0;
7796
7797 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7798     /*
7799      * Kludge to ignore BEL characters
7800      */
7801     while (*message == '\007') message++;
7802
7803     /*
7804      * [HGM] engine debug message: ignore lines starting with '#' character
7805      */
7806     if(cps->debug && *message == '#') return;
7807
7808     /*
7809      * Look for book output
7810      */
7811     if (cps == &first && bookRequested) {
7812         if (message[0] == '\t' || message[0] == ' ') {
7813             /* Part of the book output is here; append it */
7814             strcat(bookOutput, message);
7815             strcat(bookOutput, "  \n");
7816             return;
7817         } else if (bookOutput[0] != NULLCHAR) {
7818             /* All of book output has arrived; display it */
7819             char *p = bookOutput;
7820             while (*p != NULLCHAR) {
7821                 if (*p == '\t') *p = ' ';
7822                 p++;
7823             }
7824             DisplayInformation(bookOutput);
7825             bookRequested = FALSE;
7826             /* Fall through to parse the current output */
7827         }
7828     }
7829
7830     /*
7831      * Look for machine move.
7832      */
7833     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7834         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7835     {
7836         /* This method is only useful on engines that support ping */
7837         if (cps->lastPing != cps->lastPong) {
7838           if (gameMode == BeginningOfGame) {
7839             /* Extra move from before last new; ignore */
7840             if (appData.debugMode) {
7841                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7842             }
7843           } else {
7844             if (appData.debugMode) {
7845                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7846                         cps->which, gameMode);
7847             }
7848
7849             SendToProgram("undo\n", cps);
7850           }
7851           return;
7852         }
7853
7854         switch (gameMode) {
7855           case BeginningOfGame:
7856             /* Extra move from before last reset; ignore */
7857             if (appData.debugMode) {
7858                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7859             }
7860             return;
7861
7862           case EndOfGame:
7863           case IcsIdle:
7864           default:
7865             /* Extra move after we tried to stop.  The mode test is
7866                not a reliable way of detecting this problem, but it's
7867                the best we can do on engines that don't support ping.
7868             */
7869             if (appData.debugMode) {
7870                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7871                         cps->which, gameMode);
7872             }
7873             SendToProgram("undo\n", cps);
7874             return;
7875
7876           case MachinePlaysWhite:
7877           case IcsPlayingWhite:
7878             machineWhite = TRUE;
7879             break;
7880
7881           case MachinePlaysBlack:
7882           case IcsPlayingBlack:
7883             machineWhite = FALSE;
7884             break;
7885
7886           case TwoMachinesPlay:
7887             machineWhite = (cps->twoMachinesColor[0] == 'w');
7888             break;
7889         }
7890         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7891             if (appData.debugMode) {
7892                 fprintf(debugFP,
7893                         "Ignoring move out of turn by %s, gameMode %d"
7894                         ", forwardMost %d\n",
7895                         cps->which, gameMode, forwardMostMove);
7896             }
7897             return;
7898         }
7899
7900         if(cps->alphaRank) AlphaRank(machineMove, 4);
7901         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7902                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7903             /* Machine move could not be parsed; ignore it. */
7904           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7905                     machineMove, _(cps->which));
7906             DisplayError(buf1, 0);
7907             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7908                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7909             if (gameMode == TwoMachinesPlay) {
7910               GameEnds(machineWhite ? BlackWins : WhiteWins,
7911                        buf1, GE_XBOARD);
7912             }
7913             return;
7914         }
7915
7916         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7917         /* So we have to redo legality test with true e.p. status here,  */
7918         /* to make sure an illegal e.p. capture does not slip through,   */
7919         /* to cause a forfeit on a justified illegal-move complaint      */
7920         /* of the opponent.                                              */
7921         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7922            ChessMove moveType;
7923            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7924                              fromY, fromX, toY, toX, promoChar);
7925             if(moveType == IllegalMove) {
7926               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7927                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7928                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7929                            buf1, GE_XBOARD);
7930                 return;
7931            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7932            /* [HGM] Kludge to handle engines that send FRC-style castling
7933               when they shouldn't (like TSCP-Gothic) */
7934            switch(moveType) {
7935              case WhiteASideCastleFR:
7936              case BlackASideCastleFR:
7937                toX+=2;
7938                currentMoveString[2]++;
7939                break;
7940              case WhiteHSideCastleFR:
7941              case BlackHSideCastleFR:
7942                toX--;
7943                currentMoveString[2]--;
7944                break;
7945              default: ; // nothing to do, but suppresses warning of pedantic compilers
7946            }
7947         }
7948         hintRequested = FALSE;
7949         lastHint[0] = NULLCHAR;
7950         bookRequested = FALSE;
7951         /* Program may be pondering now */
7952         cps->maybeThinking = TRUE;
7953         if (cps->sendTime == 2) cps->sendTime = 1;
7954         if (cps->offeredDraw) cps->offeredDraw--;
7955
7956         /* [AS] Save move info*/
7957         pvInfoList[ forwardMostMove ].score = programStats.score;
7958         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7959         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7960
7961         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7962
7963         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7964         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7965             int count = 0;
7966
7967             while( count < adjudicateLossPlies ) {
7968                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7969
7970                 if( count & 1 ) {
7971                     score = -score; /* Flip score for winning side */
7972                 }
7973
7974                 if( score > adjudicateLossThreshold ) {
7975                     break;
7976                 }
7977
7978                 count++;
7979             }
7980
7981             if( count >= adjudicateLossPlies ) {
7982                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7983
7984                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7985                     "Xboard adjudication",
7986                     GE_XBOARD );
7987
7988                 return;
7989             }
7990         }
7991
7992         if(Adjudicate(cps)) {
7993             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7994             return; // [HGM] adjudicate: for all automatic game ends
7995         }
7996
7997 #if ZIPPY
7998         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7999             first.initDone) {
8000           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8001                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8002                 SendToICS("draw ");
8003                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8004           }
8005           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8006           ics_user_moved = 1;
8007           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8008                 char buf[3*MSG_SIZ];
8009
8010                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8011                         programStats.score / 100.,
8012                         programStats.depth,
8013                         programStats.time / 100.,
8014                         (unsigned int)programStats.nodes,
8015                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8016                         programStats.movelist);
8017                 SendToICS(buf);
8018 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8019           }
8020         }
8021 #endif
8022
8023         /* [AS] Clear stats for next move */
8024         ClearProgramStats();
8025         thinkOutput[0] = NULLCHAR;
8026         hiddenThinkOutputState = 0;
8027
8028         bookHit = NULL;
8029         if (gameMode == TwoMachinesPlay) {
8030             /* [HGM] relaying draw offers moved to after reception of move */
8031             /* and interpreting offer as claim if it brings draw condition */
8032             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8033                 SendToProgram("draw\n", cps->other);
8034             }
8035             if (cps->other->sendTime) {
8036                 SendTimeRemaining(cps->other,
8037                                   cps->other->twoMachinesColor[0] == 'w');
8038             }
8039             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8040             if (firstMove && !bookHit) {
8041                 firstMove = FALSE;
8042                 if (cps->other->useColors) {
8043                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8044                 }
8045                 SendToProgram("go\n", cps->other);
8046             }
8047             cps->other->maybeThinking = TRUE;
8048         }
8049
8050         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8051
8052         if (!pausing && appData.ringBellAfterMoves) {
8053             RingBell();
8054         }
8055
8056         /*
8057          * Reenable menu items that were disabled while
8058          * machine was thinking
8059          */
8060         if (gameMode != TwoMachinesPlay)
8061             SetUserThinkingEnables();
8062
8063         // [HGM] book: after book hit opponent has received move and is now in force mode
8064         // force the book reply into it, and then fake that it outputted this move by jumping
8065         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8066         if(bookHit) {
8067                 static char bookMove[MSG_SIZ]; // a bit generous?
8068
8069                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8070                 strcat(bookMove, bookHit);
8071                 message = bookMove;
8072                 cps = cps->other;
8073                 programStats.nodes = programStats.depth = programStats.time =
8074                 programStats.score = programStats.got_only_move = 0;
8075                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8076
8077                 if(cps->lastPing != cps->lastPong) {
8078                     savedMessage = message; // args for deferred call
8079                     savedState = cps;
8080                     ScheduleDelayedEvent(DeferredBookMove, 10);
8081                     return;
8082                 }
8083                 goto FakeBookMove;
8084         }
8085
8086         return;
8087     }
8088
8089     /* Set special modes for chess engines.  Later something general
8090      *  could be added here; for now there is just one kludge feature,
8091      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8092      *  when "xboard" is given as an interactive command.
8093      */
8094     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8095         cps->useSigint = FALSE;
8096         cps->useSigterm = FALSE;
8097     }
8098     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8099       ParseFeatures(message+8, cps);
8100       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8101     }
8102
8103     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8104                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8105       int dummy, s=6; char buf[MSG_SIZ];
8106       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8107       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8108       if(startedFromSetupPosition) return;
8109       ParseFEN(boards[0], &dummy, message+s);
8110       DrawPosition(TRUE, boards[0]);
8111       startedFromSetupPosition = TRUE;
8112       return;
8113     }
8114     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8115      * want this, I was asked to put it in, and obliged.
8116      */
8117     if (!strncmp(message, "setboard ", 9)) {
8118         Board initial_position;
8119
8120         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8121
8122         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8123             DisplayError(_("Bad FEN received from engine"), 0);
8124             return ;
8125         } else {
8126            Reset(TRUE, FALSE);
8127            CopyBoard(boards[0], initial_position);
8128            initialRulePlies = FENrulePlies;
8129            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8130            else gameMode = MachinePlaysBlack;
8131            DrawPosition(FALSE, boards[currentMove]);
8132         }
8133         return;
8134     }
8135
8136     /*
8137      * Look for communication commands
8138      */
8139     if (!strncmp(message, "telluser ", 9)) {
8140         if(message[9] == '\\' && message[10] == '\\')
8141             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8142         PlayTellSound();
8143         DisplayNote(message + 9);
8144         return;
8145     }
8146     if (!strncmp(message, "tellusererror ", 14)) {
8147         cps->userError = 1;
8148         if(message[14] == '\\' && message[15] == '\\')
8149             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8150         PlayTellSound();
8151         DisplayError(message + 14, 0);
8152         return;
8153     }
8154     if (!strncmp(message, "tellopponent ", 13)) {
8155       if (appData.icsActive) {
8156         if (loggedOn) {
8157           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8158           SendToICS(buf1);
8159         }
8160       } else {
8161         DisplayNote(message + 13);
8162       }
8163       return;
8164     }
8165     if (!strncmp(message, "tellothers ", 11)) {
8166       if (appData.icsActive) {
8167         if (loggedOn) {
8168           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8169           SendToICS(buf1);
8170         }
8171       }
8172       return;
8173     }
8174     if (!strncmp(message, "tellall ", 8)) {
8175       if (appData.icsActive) {
8176         if (loggedOn) {
8177           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8178           SendToICS(buf1);
8179         }
8180       } else {
8181         DisplayNote(message + 8);
8182       }
8183       return;
8184     }
8185     if (strncmp(message, "warning", 7) == 0) {
8186         /* Undocumented feature, use tellusererror in new code */
8187         DisplayError(message, 0);
8188         return;
8189     }
8190     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8191         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8192         strcat(realname, " query");
8193         AskQuestion(realname, buf2, buf1, cps->pr);
8194         return;
8195     }
8196     /* Commands from the engine directly to ICS.  We don't allow these to be
8197      *  sent until we are logged on. Crafty kibitzes have been known to
8198      *  interfere with the login process.
8199      */
8200     if (loggedOn) {
8201         if (!strncmp(message, "tellics ", 8)) {
8202             SendToICS(message + 8);
8203             SendToICS("\n");
8204             return;
8205         }
8206         if (!strncmp(message, "tellicsnoalias ", 15)) {
8207             SendToICS(ics_prefix);
8208             SendToICS(message + 15);
8209             SendToICS("\n");
8210             return;
8211         }
8212         /* The following are for backward compatibility only */
8213         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8214             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8215             SendToICS(ics_prefix);
8216             SendToICS(message);
8217             SendToICS("\n");
8218             return;
8219         }
8220     }
8221     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8222         return;
8223     }
8224     /*
8225      * If the move is illegal, cancel it and redraw the board.
8226      * Also deal with other error cases.  Matching is rather loose
8227      * here to accommodate engines written before the spec.
8228      */
8229     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8230         strncmp(message, "Error", 5) == 0) {
8231         if (StrStr(message, "name") ||
8232             StrStr(message, "rating") || StrStr(message, "?") ||
8233             StrStr(message, "result") || StrStr(message, "board") ||
8234             StrStr(message, "bk") || StrStr(message, "computer") ||
8235             StrStr(message, "variant") || StrStr(message, "hint") ||
8236             StrStr(message, "random") || StrStr(message, "depth") ||
8237             StrStr(message, "accepted")) {
8238             return;
8239         }
8240         if (StrStr(message, "protover")) {
8241           /* Program is responding to input, so it's apparently done
8242              initializing, and this error message indicates it is
8243              protocol version 1.  So we don't need to wait any longer
8244              for it to initialize and send feature commands. */
8245           FeatureDone(cps, 1);
8246           cps->protocolVersion = 1;
8247           return;
8248         }
8249         cps->maybeThinking = FALSE;
8250
8251         if (StrStr(message, "draw")) {
8252             /* Program doesn't have "draw" command */
8253             cps->sendDrawOffers = 0;
8254             return;
8255         }
8256         if (cps->sendTime != 1 &&
8257             (StrStr(message, "time") || StrStr(message, "otim"))) {
8258           /* Program apparently doesn't have "time" or "otim" command */
8259           cps->sendTime = 0;
8260           return;
8261         }
8262         if (StrStr(message, "analyze")) {
8263             cps->analysisSupport = FALSE;
8264             cps->analyzing = FALSE;
8265 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8266             EditGameEvent(); // [HGM] try to preserve loaded game
8267             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8268             DisplayError(buf2, 0);
8269             return;
8270         }
8271         if (StrStr(message, "(no matching move)st")) {
8272           /* Special kludge for GNU Chess 4 only */
8273           cps->stKludge = TRUE;
8274           SendTimeControl(cps, movesPerSession, timeControl,
8275                           timeIncrement, appData.searchDepth,
8276                           searchTime);
8277           return;
8278         }
8279         if (StrStr(message, "(no matching move)sd")) {
8280           /* Special kludge for GNU Chess 4 only */
8281           cps->sdKludge = TRUE;
8282           SendTimeControl(cps, movesPerSession, timeControl,
8283                           timeIncrement, appData.searchDepth,
8284                           searchTime);
8285           return;
8286         }
8287         if (!StrStr(message, "llegal")) {
8288             return;
8289         }
8290         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8291             gameMode == IcsIdle) return;
8292         if (forwardMostMove <= backwardMostMove) return;
8293         if (pausing) PauseEvent();
8294       if(appData.forceIllegal) {
8295             // [HGM] illegal: machine refused move; force position after move into it
8296           SendToProgram("force\n", cps);
8297           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8298                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8299                 // when black is to move, while there might be nothing on a2 or black
8300                 // might already have the move. So send the board as if white has the move.
8301                 // But first we must change the stm of the engine, as it refused the last move
8302                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8303                 if(WhiteOnMove(forwardMostMove)) {
8304                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8305                     SendBoard(cps, forwardMostMove); // kludgeless board
8306                 } else {
8307                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8308                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8309                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8310                 }
8311           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8312             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8313                  gameMode == TwoMachinesPlay)
8314               SendToProgram("go\n", cps);
8315             return;
8316       } else
8317         if (gameMode == PlayFromGameFile) {
8318             /* Stop reading this game file */
8319             gameMode = EditGame;
8320             ModeHighlight();
8321         }
8322         /* [HGM] illegal-move claim should forfeit game when Xboard */
8323         /* only passes fully legal moves                            */
8324         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8325             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8326                                 "False illegal-move claim", GE_XBOARD );
8327             return; // do not take back move we tested as valid
8328         }
8329         currentMove = forwardMostMove-1;
8330         DisplayMove(currentMove-1); /* before DisplayMoveError */
8331         SwitchClocks(forwardMostMove-1); // [HGM] race
8332         DisplayBothClocks();
8333         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8334                 parseList[currentMove], _(cps->which));
8335         DisplayMoveError(buf1);
8336         DrawPosition(FALSE, boards[currentMove]);
8337
8338         SetUserThinkingEnables();
8339         return;
8340     }
8341     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8342         /* Program has a broken "time" command that
8343            outputs a string not ending in newline.
8344            Don't use it. */
8345         cps->sendTime = 0;
8346     }
8347
8348     /*
8349      * If chess program startup fails, exit with an error message.
8350      * Attempts to recover here are futile.
8351      */
8352     if ((StrStr(message, "unknown host") != NULL)
8353         || (StrStr(message, "No remote directory") != NULL)
8354         || (StrStr(message, "not found") != NULL)
8355         || (StrStr(message, "No such file") != NULL)
8356         || (StrStr(message, "can't alloc") != NULL)
8357         || (StrStr(message, "Permission denied") != NULL)) {
8358
8359         cps->maybeThinking = FALSE;
8360         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8361                 _(cps->which), cps->program, cps->host, message);
8362         RemoveInputSource(cps->isr);
8363         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8364             if(cps == &first) appData.noChessProgram = TRUE;
8365             DisplayError(buf1, 0);
8366         }
8367         return;
8368     }
8369
8370     /*
8371      * Look for hint output
8372      */
8373     if (sscanf(message, "Hint: %s", buf1) == 1) {
8374         if (cps == &first && hintRequested) {
8375             hintRequested = FALSE;
8376             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8377                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8378                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8379                                     PosFlags(forwardMostMove),
8380                                     fromY, fromX, toY, toX, promoChar, buf1);
8381                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8382                 DisplayInformation(buf2);
8383             } else {
8384                 /* Hint move could not be parsed!? */
8385               snprintf(buf2, sizeof(buf2),
8386                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8387                         buf1, _(cps->which));
8388                 DisplayError(buf2, 0);
8389             }
8390         } else {
8391           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8392         }
8393         return;
8394     }
8395
8396     /*
8397      * Ignore other messages if game is not in progress
8398      */
8399     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8400         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8401
8402     /*
8403      * look for win, lose, draw, or draw offer
8404      */
8405     if (strncmp(message, "1-0", 3) == 0) {
8406         char *p, *q, *r = "";
8407         p = strchr(message, '{');
8408         if (p) {
8409             q = strchr(p, '}');
8410             if (q) {
8411                 *q = NULLCHAR;
8412                 r = p + 1;
8413             }
8414         }
8415         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8416         return;
8417     } else if (strncmp(message, "0-1", 3) == 0) {
8418         char *p, *q, *r = "";
8419         p = strchr(message, '{');
8420         if (p) {
8421             q = strchr(p, '}');
8422             if (q) {
8423                 *q = NULLCHAR;
8424                 r = p + 1;
8425             }
8426         }
8427         /* Kludge for Arasan 4.1 bug */
8428         if (strcmp(r, "Black resigns") == 0) {
8429             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8430             return;
8431         }
8432         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8433         return;
8434     } else if (strncmp(message, "1/2", 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
8445         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8446         return;
8447
8448     } else if (strncmp(message, "White resign", 12) == 0) {
8449         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8450         return;
8451     } else if (strncmp(message, "Black resign", 12) == 0) {
8452         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8453         return;
8454     } else if (strncmp(message, "White matches", 13) == 0 ||
8455                strncmp(message, "Black matches", 13) == 0   ) {
8456         /* [HGM] ignore GNUShogi noises */
8457         return;
8458     } else if (strncmp(message, "White", 5) == 0 &&
8459                message[5] != '(' &&
8460                StrStr(message, "Black") == NULL) {
8461         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8462         return;
8463     } else if (strncmp(message, "Black", 5) == 0 &&
8464                message[5] != '(') {
8465         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8466         return;
8467     } else if (strcmp(message, "resign") == 0 ||
8468                strcmp(message, "computer resigns") == 0) {
8469         switch (gameMode) {
8470           case MachinePlaysBlack:
8471           case IcsPlayingBlack:
8472             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8473             break;
8474           case MachinePlaysWhite:
8475           case IcsPlayingWhite:
8476             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8477             break;
8478           case TwoMachinesPlay:
8479             if (cps->twoMachinesColor[0] == 'w')
8480               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8481             else
8482               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8483             break;
8484           default:
8485             /* can't happen */
8486             break;
8487         }
8488         return;
8489     } else if (strncmp(message, "opponent mates", 14) == 0) {
8490         switch (gameMode) {
8491           case MachinePlaysBlack:
8492           case IcsPlayingBlack:
8493             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8494             break;
8495           case MachinePlaysWhite:
8496           case IcsPlayingWhite:
8497             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8498             break;
8499           case TwoMachinesPlay:
8500             if (cps->twoMachinesColor[0] == 'w')
8501               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8502             else
8503               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8504             break;
8505           default:
8506             /* can't happen */
8507             break;
8508         }
8509         return;
8510     } else if (strncmp(message, "computer mates", 14) == 0) {
8511         switch (gameMode) {
8512           case MachinePlaysBlack:
8513           case IcsPlayingBlack:
8514             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8515             break;
8516           case MachinePlaysWhite:
8517           case IcsPlayingWhite:
8518             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8519             break;
8520           case TwoMachinesPlay:
8521             if (cps->twoMachinesColor[0] == 'w')
8522               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8523             else
8524               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8525             break;
8526           default:
8527             /* can't happen */
8528             break;
8529         }
8530         return;
8531     } else if (strncmp(message, "checkmate", 9) == 0) {
8532         if (WhiteOnMove(forwardMostMove)) {
8533             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8534         } else {
8535             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8536         }
8537         return;
8538     } else if (strstr(message, "Draw") != NULL ||
8539                strstr(message, "game is a draw") != NULL) {
8540         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8541         return;
8542     } else if (strstr(message, "offer") != NULL &&
8543                strstr(message, "draw") != NULL) {
8544 #if ZIPPY
8545         if (appData.zippyPlay && first.initDone) {
8546             /* Relay offer to ICS */
8547             SendToICS(ics_prefix);
8548             SendToICS("draw\n");
8549         }
8550 #endif
8551         cps->offeredDraw = 2; /* valid until this engine moves twice */
8552         if (gameMode == TwoMachinesPlay) {
8553             if (cps->other->offeredDraw) {
8554                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8555             /* [HGM] in two-machine mode we delay relaying draw offer      */
8556             /* until after we also have move, to see if it is really claim */
8557             }
8558         } else if (gameMode == MachinePlaysWhite ||
8559                    gameMode == MachinePlaysBlack) {
8560           if (userOfferedDraw) {
8561             DisplayInformation(_("Machine accepts your draw offer"));
8562             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8563           } else {
8564             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8565           }
8566         }
8567     }
8568
8569
8570     /*
8571      * Look for thinking output
8572      */
8573     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8574           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8575                                 ) {
8576         int plylev, mvleft, mvtot, curscore, time;
8577         char mvname[MOVE_LEN];
8578         u64 nodes; // [DM]
8579         char plyext;
8580         int ignore = FALSE;
8581         int prefixHint = FALSE;
8582         mvname[0] = NULLCHAR;
8583
8584         switch (gameMode) {
8585           case MachinePlaysBlack:
8586           case IcsPlayingBlack:
8587             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8588             break;
8589           case MachinePlaysWhite:
8590           case IcsPlayingWhite:
8591             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8592             break;
8593           case AnalyzeMode:
8594           case AnalyzeFile:
8595             break;
8596           case IcsObserving: /* [DM] icsEngineAnalyze */
8597             if (!appData.icsEngineAnalyze) ignore = TRUE;
8598             break;
8599           case TwoMachinesPlay:
8600             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8601                 ignore = TRUE;
8602             }
8603             break;
8604           default:
8605             ignore = TRUE;
8606             break;
8607         }
8608
8609         if (!ignore) {
8610             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8611             buf1[0] = NULLCHAR;
8612             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8613                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8614
8615                 if (plyext != ' ' && plyext != '\t') {
8616                     time *= 100;
8617                 }
8618
8619                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8620                 if( cps->scoreIsAbsolute &&
8621                     ( gameMode == MachinePlaysBlack ||
8622                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8623                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8624                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8625                      !WhiteOnMove(currentMove)
8626                     ) )
8627                 {
8628                     curscore = -curscore;
8629                 }
8630
8631                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8632
8633                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8634                         char buf[MSG_SIZ];
8635                         FILE *f;
8636                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8637                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8638                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8639                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8640                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8641                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8642                                 fclose(f);
8643                         } else DisplayError(_("failed writing PV"), 0);
8644                 }
8645
8646                 tempStats.depth = plylev;
8647                 tempStats.nodes = nodes;
8648                 tempStats.time = time;
8649                 tempStats.score = curscore;
8650                 tempStats.got_only_move = 0;
8651
8652                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8653                         int ticklen;
8654
8655                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8656                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8657                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8658                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8659                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8660                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8661                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8662                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8663                 }
8664
8665                 /* Buffer overflow protection */
8666                 if (pv[0] != NULLCHAR) {
8667                     if (strlen(pv) >= sizeof(tempStats.movelist)
8668                         && appData.debugMode) {
8669                         fprintf(debugFP,
8670                                 "PV is too long; using the first %u bytes.\n",
8671                                 (unsigned) sizeof(tempStats.movelist) - 1);
8672                     }
8673
8674                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8675                 } else {
8676                     sprintf(tempStats.movelist, " no PV\n");
8677                 }
8678
8679                 if (tempStats.seen_stat) {
8680                     tempStats.ok_to_send = 1;
8681                 }
8682
8683                 if (strchr(tempStats.movelist, '(') != NULL) {
8684                     tempStats.line_is_book = 1;
8685                     tempStats.nr_moves = 0;
8686                     tempStats.moves_left = 0;
8687                 } else {
8688                     tempStats.line_is_book = 0;
8689                 }
8690
8691                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8692                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8693
8694                 SendProgramStatsToFrontend( cps, &tempStats );
8695
8696                 /*
8697                     [AS] Protect the thinkOutput buffer from overflow... this
8698                     is only useful if buf1 hasn't overflowed first!
8699                 */
8700                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8701                          plylev,
8702                          (gameMode == TwoMachinesPlay ?
8703                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8704                          ((double) curscore) / 100.0,
8705                          prefixHint ? lastHint : "",
8706                          prefixHint ? " " : "" );
8707
8708                 if( buf1[0] != NULLCHAR ) {
8709                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8710
8711                     if( strlen(pv) > max_len ) {
8712                         if( appData.debugMode) {
8713                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8714                         }
8715                         pv[max_len+1] = '\0';
8716                     }
8717
8718                     strcat( thinkOutput, pv);
8719                 }
8720
8721                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8722                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8723                     DisplayMove(currentMove - 1);
8724                 }
8725                 return;
8726
8727             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8728                 /* crafty (9.25+) says "(only move) <move>"
8729                  * if there is only 1 legal move
8730                  */
8731                 sscanf(p, "(only move) %s", buf1);
8732                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8733                 sprintf(programStats.movelist, "%s (only move)", buf1);
8734                 programStats.depth = 1;
8735                 programStats.nr_moves = 1;
8736                 programStats.moves_left = 1;
8737                 programStats.nodes = 1;
8738                 programStats.time = 1;
8739                 programStats.got_only_move = 1;
8740
8741                 /* Not really, but we also use this member to
8742                    mean "line isn't going to change" (Crafty
8743                    isn't searching, so stats won't change) */
8744                 programStats.line_is_book = 1;
8745
8746                 SendProgramStatsToFrontend( cps, &programStats );
8747
8748                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8749                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8750                     DisplayMove(currentMove - 1);
8751                 }
8752                 return;
8753             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8754                               &time, &nodes, &plylev, &mvleft,
8755                               &mvtot, mvname) >= 5) {
8756                 /* The stat01: line is from Crafty (9.29+) in response
8757                    to the "." command */
8758                 programStats.seen_stat = 1;
8759                 cps->maybeThinking = TRUE;
8760
8761                 if (programStats.got_only_move || !appData.periodicUpdates)
8762                   return;
8763
8764                 programStats.depth = plylev;
8765                 programStats.time = time;
8766                 programStats.nodes = nodes;
8767                 programStats.moves_left = mvleft;
8768                 programStats.nr_moves = mvtot;
8769                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8770                 programStats.ok_to_send = 1;
8771                 programStats.movelist[0] = '\0';
8772
8773                 SendProgramStatsToFrontend( cps, &programStats );
8774
8775                 return;
8776
8777             } else if (strncmp(message,"++",2) == 0) {
8778                 /* Crafty 9.29+ outputs this */
8779                 programStats.got_fail = 2;
8780                 return;
8781
8782             } else if (strncmp(message,"--",2) == 0) {
8783                 /* Crafty 9.29+ outputs this */
8784                 programStats.got_fail = 1;
8785                 return;
8786
8787             } else if (thinkOutput[0] != NULLCHAR &&
8788                        strncmp(message, "    ", 4) == 0) {
8789                 unsigned message_len;
8790
8791                 p = message;
8792                 while (*p && *p == ' ') p++;
8793
8794                 message_len = strlen( p );
8795
8796                 /* [AS] Avoid buffer overflow */
8797                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8798                     strcat(thinkOutput, " ");
8799                     strcat(thinkOutput, p);
8800                 }
8801
8802                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8803                     strcat(programStats.movelist, " ");
8804                     strcat(programStats.movelist, p);
8805                 }
8806
8807                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8808                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8809                     DisplayMove(currentMove - 1);
8810                 }
8811                 return;
8812             }
8813         }
8814         else {
8815             buf1[0] = NULLCHAR;
8816
8817             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8818                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8819             {
8820                 ChessProgramStats cpstats;
8821
8822                 if (plyext != ' ' && plyext != '\t') {
8823                     time *= 100;
8824                 }
8825
8826                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8827                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8828                     curscore = -curscore;
8829                 }
8830
8831                 cpstats.depth = plylev;
8832                 cpstats.nodes = nodes;
8833                 cpstats.time = time;
8834                 cpstats.score = curscore;
8835                 cpstats.got_only_move = 0;
8836                 cpstats.movelist[0] = '\0';
8837
8838                 if (buf1[0] != NULLCHAR) {
8839                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8840                 }
8841
8842                 cpstats.ok_to_send = 0;
8843                 cpstats.line_is_book = 0;
8844                 cpstats.nr_moves = 0;
8845                 cpstats.moves_left = 0;
8846
8847                 SendProgramStatsToFrontend( cps, &cpstats );
8848             }
8849         }
8850     }
8851 }
8852
8853
8854 /* Parse a game score from the character string "game", and
8855    record it as the history of the current game.  The game
8856    score is NOT assumed to start from the standard position.
8857    The display is not updated in any way.
8858    */
8859 void
8860 ParseGameHistory (char *game)
8861 {
8862     ChessMove moveType;
8863     int fromX, fromY, toX, toY, boardIndex;
8864     char promoChar;
8865     char *p, *q;
8866     char buf[MSG_SIZ];
8867
8868     if (appData.debugMode)
8869       fprintf(debugFP, "Parsing game history: %s\n", game);
8870
8871     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8872     gameInfo.site = StrSave(appData.icsHost);
8873     gameInfo.date = PGNDate();
8874     gameInfo.round = StrSave("-");
8875
8876     /* Parse out names of players */
8877     while (*game == ' ') game++;
8878     p = buf;
8879     while (*game != ' ') *p++ = *game++;
8880     *p = NULLCHAR;
8881     gameInfo.white = StrSave(buf);
8882     while (*game == ' ') game++;
8883     p = buf;
8884     while (*game != ' ' && *game != '\n') *p++ = *game++;
8885     *p = NULLCHAR;
8886     gameInfo.black = StrSave(buf);
8887
8888     /* Parse moves */
8889     boardIndex = blackPlaysFirst ? 1 : 0;
8890     yynewstr(game);
8891     for (;;) {
8892         yyboardindex = boardIndex;
8893         moveType = (ChessMove) Myylex();
8894         switch (moveType) {
8895           case IllegalMove:             /* maybe suicide chess, etc. */
8896   if (appData.debugMode) {
8897     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8898     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8899     setbuf(debugFP, NULL);
8900   }
8901           case WhitePromotion:
8902           case BlackPromotion:
8903           case WhiteNonPromotion:
8904           case BlackNonPromotion:
8905           case NormalMove:
8906           case WhiteCapturesEnPassant:
8907           case BlackCapturesEnPassant:
8908           case WhiteKingSideCastle:
8909           case WhiteQueenSideCastle:
8910           case BlackKingSideCastle:
8911           case BlackQueenSideCastle:
8912           case WhiteKingSideCastleWild:
8913           case WhiteQueenSideCastleWild:
8914           case BlackKingSideCastleWild:
8915           case BlackQueenSideCastleWild:
8916           /* PUSH Fabien */
8917           case WhiteHSideCastleFR:
8918           case WhiteASideCastleFR:
8919           case BlackHSideCastleFR:
8920           case BlackASideCastleFR:
8921           /* POP Fabien */
8922             fromX = currentMoveString[0] - AAA;
8923             fromY = currentMoveString[1] - ONE;
8924             toX = currentMoveString[2] - AAA;
8925             toY = currentMoveString[3] - ONE;
8926             promoChar = currentMoveString[4];
8927             break;
8928           case WhiteDrop:
8929           case BlackDrop:
8930             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8931             fromX = moveType == WhiteDrop ?
8932               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8933             (int) CharToPiece(ToLower(currentMoveString[0]));
8934             fromY = DROP_RANK;
8935             toX = currentMoveString[2] - AAA;
8936             toY = currentMoveString[3] - ONE;
8937             promoChar = NULLCHAR;
8938             break;
8939           case AmbiguousMove:
8940             /* bug? */
8941             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8942   if (appData.debugMode) {
8943     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8944     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8945     setbuf(debugFP, NULL);
8946   }
8947             DisplayError(buf, 0);
8948             return;
8949           case ImpossibleMove:
8950             /* bug? */
8951             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8952   if (appData.debugMode) {
8953     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8954     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8955     setbuf(debugFP, NULL);
8956   }
8957             DisplayError(buf, 0);
8958             return;
8959           case EndOfFile:
8960             if (boardIndex < backwardMostMove) {
8961                 /* Oops, gap.  How did that happen? */
8962                 DisplayError(_("Gap in move list"), 0);
8963                 return;
8964             }
8965             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8966             if (boardIndex > forwardMostMove) {
8967                 forwardMostMove = boardIndex;
8968             }
8969             return;
8970           case ElapsedTime:
8971             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8972                 strcat(parseList[boardIndex-1], " ");
8973                 strcat(parseList[boardIndex-1], yy_text);
8974             }
8975             continue;
8976           case Comment:
8977           case PGNTag:
8978           case NAG:
8979           default:
8980             /* ignore */
8981             continue;
8982           case WhiteWins:
8983           case BlackWins:
8984           case GameIsDrawn:
8985           case GameUnfinished:
8986             if (gameMode == IcsExamining) {
8987                 if (boardIndex < backwardMostMove) {
8988                     /* Oops, gap.  How did that happen? */
8989                     return;
8990                 }
8991                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8992                 return;
8993             }
8994             gameInfo.result = moveType;
8995             p = strchr(yy_text, '{');
8996             if (p == NULL) p = strchr(yy_text, '(');
8997             if (p == NULL) {
8998                 p = yy_text;
8999                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9000             } else {
9001                 q = strchr(p, *p == '{' ? '}' : ')');
9002                 if (q != NULL) *q = NULLCHAR;
9003                 p++;
9004             }
9005             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9006             gameInfo.resultDetails = StrSave(p);
9007             continue;
9008         }
9009         if (boardIndex >= forwardMostMove &&
9010             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9011             backwardMostMove = blackPlaysFirst ? 1 : 0;
9012             return;
9013         }
9014         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9015                                  fromY, fromX, toY, toX, promoChar,
9016                                  parseList[boardIndex]);
9017         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9018         /* currentMoveString is set as a side-effect of yylex */
9019         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9020         strcat(moveList[boardIndex], "\n");
9021         boardIndex++;
9022         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9023         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9024           case MT_NONE:
9025           case MT_STALEMATE:
9026           default:
9027             break;
9028           case MT_CHECK:
9029             if(gameInfo.variant != VariantShogi)
9030                 strcat(parseList[boardIndex - 1], "+");
9031             break;
9032           case MT_CHECKMATE:
9033           case MT_STAINMATE:
9034             strcat(parseList[boardIndex - 1], "#");
9035             break;
9036         }
9037     }
9038 }
9039
9040
9041 /* Apply a move to the given board  */
9042 void
9043 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9044 {
9045   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9046   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9047
9048     /* [HGM] compute & store e.p. status and castling rights for new position */
9049     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9050
9051       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9052       oldEP = (signed char)board[EP_STATUS];
9053       board[EP_STATUS] = EP_NONE;
9054
9055   if (fromY == DROP_RANK) {
9056         /* must be first */
9057         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9058             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9059             return;
9060         }
9061         piece = board[toY][toX] = (ChessSquare) fromX;
9062   } else {
9063       int i;
9064
9065       if( board[toY][toX] != EmptySquare )
9066            board[EP_STATUS] = EP_CAPTURE;
9067
9068       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9069            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9070                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9071       } else
9072       if( board[fromY][fromX] == WhitePawn ) {
9073            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9074                board[EP_STATUS] = EP_PAWN_MOVE;
9075            if( toY-fromY==2) {
9076                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9077                         gameInfo.variant != VariantBerolina || toX < fromX)
9078                       board[EP_STATUS] = toX | berolina;
9079                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9080                         gameInfo.variant != VariantBerolina || toX > fromX)
9081                       board[EP_STATUS] = toX;
9082            }
9083       } else
9084       if( board[fromY][fromX] == BlackPawn ) {
9085            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9086                board[EP_STATUS] = EP_PAWN_MOVE;
9087            if( toY-fromY== -2) {
9088                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9089                         gameInfo.variant != VariantBerolina || toX < fromX)
9090                       board[EP_STATUS] = toX | berolina;
9091                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9092                         gameInfo.variant != VariantBerolina || toX > fromX)
9093                       board[EP_STATUS] = toX;
9094            }
9095        }
9096
9097        for(i=0; i<nrCastlingRights; i++) {
9098            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9099               board[CASTLING][i] == toX   && castlingRank[i] == toY
9100              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9101        }
9102
9103      if (fromX == toX && fromY == toY) return;
9104
9105      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9106      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9107      if(gameInfo.variant == VariantKnightmate)
9108          king += (int) WhiteUnicorn - (int) WhiteKing;
9109
9110     /* Code added by Tord: */
9111     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9112     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9113         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9114       board[fromY][fromX] = EmptySquare;
9115       board[toY][toX] = EmptySquare;
9116       if((toX > fromX) != (piece == WhiteRook)) {
9117         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9118       } else {
9119         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9120       }
9121     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9122                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9123       board[fromY][fromX] = EmptySquare;
9124       board[toY][toX] = EmptySquare;
9125       if((toX > fromX) != (piece == BlackRook)) {
9126         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9127       } else {
9128         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9129       }
9130     /* End of code added by Tord */
9131
9132     } else if (board[fromY][fromX] == king
9133         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9134         && toY == fromY && toX > fromX+1) {
9135         board[fromY][fromX] = EmptySquare;
9136         board[toY][toX] = king;
9137         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9138         board[fromY][BOARD_RGHT-1] = EmptySquare;
9139     } else if (board[fromY][fromX] == king
9140         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9141                && toY == fromY && toX < fromX-1) {
9142         board[fromY][fromX] = EmptySquare;
9143         board[toY][toX] = king;
9144         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9145         board[fromY][BOARD_LEFT] = EmptySquare;
9146     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9147                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9148                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9149                ) {
9150         /* white pawn promotion */
9151         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9152         if(gameInfo.variant==VariantBughouse ||
9153            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9154             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9155         board[fromY][fromX] = EmptySquare;
9156     } else if ((fromY >= BOARD_HEIGHT>>1)
9157                && (toX != fromX)
9158                && gameInfo.variant != VariantXiangqi
9159                && gameInfo.variant != VariantBerolina
9160                && (board[fromY][fromX] == WhitePawn)
9161                && (board[toY][toX] == EmptySquare)) {
9162         board[fromY][fromX] = EmptySquare;
9163         board[toY][toX] = WhitePawn;
9164         captured = board[toY - 1][toX];
9165         board[toY - 1][toX] = EmptySquare;
9166     } else if ((fromY == BOARD_HEIGHT-4)
9167                && (toX == fromX)
9168                && gameInfo.variant == VariantBerolina
9169                && (board[fromY][fromX] == WhitePawn)
9170                && (board[toY][toX] == EmptySquare)) {
9171         board[fromY][fromX] = EmptySquare;
9172         board[toY][toX] = WhitePawn;
9173         if(oldEP & EP_BEROLIN_A) {
9174                 captured = board[fromY][fromX-1];
9175                 board[fromY][fromX-1] = EmptySquare;
9176         }else{  captured = board[fromY][fromX+1];
9177                 board[fromY][fromX+1] = EmptySquare;
9178         }
9179     } else if (board[fromY][fromX] == king
9180         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9181                && toY == fromY && toX > fromX+1) {
9182         board[fromY][fromX] = EmptySquare;
9183         board[toY][toX] = king;
9184         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9185         board[fromY][BOARD_RGHT-1] = EmptySquare;
9186     } else if (board[fromY][fromX] == king
9187         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9188                && toY == fromY && toX < fromX-1) {
9189         board[fromY][fromX] = EmptySquare;
9190         board[toY][toX] = king;
9191         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9192         board[fromY][BOARD_LEFT] = EmptySquare;
9193     } else if (fromY == 7 && fromX == 3
9194                && board[fromY][fromX] == BlackKing
9195                && toY == 7 && toX == 5) {
9196         board[fromY][fromX] = EmptySquare;
9197         board[toY][toX] = BlackKing;
9198         board[fromY][7] = EmptySquare;
9199         board[toY][4] = BlackRook;
9200     } else if (fromY == 7 && fromX == 3
9201                && board[fromY][fromX] == BlackKing
9202                && toY == 7 && toX == 1) {
9203         board[fromY][fromX] = EmptySquare;
9204         board[toY][toX] = BlackKing;
9205         board[fromY][0] = EmptySquare;
9206         board[toY][2] = BlackRook;
9207     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9208                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9209                && toY < promoRank && promoChar
9210                ) {
9211         /* black pawn promotion */
9212         board[toY][toX] = CharToPiece(ToLower(promoChar));
9213         if(gameInfo.variant==VariantBughouse ||
9214            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9215             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9216         board[fromY][fromX] = EmptySquare;
9217     } else if ((fromY < BOARD_HEIGHT>>1)
9218                && (toX != fromX)
9219                && gameInfo.variant != VariantXiangqi
9220                && gameInfo.variant != VariantBerolina
9221                && (board[fromY][fromX] == BlackPawn)
9222                && (board[toY][toX] == EmptySquare)) {
9223         board[fromY][fromX] = EmptySquare;
9224         board[toY][toX] = BlackPawn;
9225         captured = board[toY + 1][toX];
9226         board[toY + 1][toX] = EmptySquare;
9227     } else if ((fromY == 3)
9228                && (toX == fromX)
9229                && gameInfo.variant == VariantBerolina
9230                && (board[fromY][fromX] == BlackPawn)
9231                && (board[toY][toX] == EmptySquare)) {
9232         board[fromY][fromX] = EmptySquare;
9233         board[toY][toX] = BlackPawn;
9234         if(oldEP & EP_BEROLIN_A) {
9235                 captured = board[fromY][fromX-1];
9236                 board[fromY][fromX-1] = EmptySquare;
9237         }else{  captured = board[fromY][fromX+1];
9238                 board[fromY][fromX+1] = EmptySquare;
9239         }
9240     } else {
9241         board[toY][toX] = board[fromY][fromX];
9242         board[fromY][fromX] = EmptySquare;
9243     }
9244   }
9245
9246     if (gameInfo.holdingsWidth != 0) {
9247
9248       /* !!A lot more code needs to be written to support holdings  */
9249       /* [HGM] OK, so I have written it. Holdings are stored in the */
9250       /* penultimate board files, so they are automaticlly stored   */
9251       /* in the game history.                                       */
9252       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9253                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9254         /* Delete from holdings, by decreasing count */
9255         /* and erasing image if necessary            */
9256         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9257         if(p < (int) BlackPawn) { /* white drop */
9258              p -= (int)WhitePawn;
9259                  p = PieceToNumber((ChessSquare)p);
9260              if(p >= gameInfo.holdingsSize) p = 0;
9261              if(--board[p][BOARD_WIDTH-2] <= 0)
9262                   board[p][BOARD_WIDTH-1] = EmptySquare;
9263              if((int)board[p][BOARD_WIDTH-2] < 0)
9264                         board[p][BOARD_WIDTH-2] = 0;
9265         } else {                  /* black drop */
9266              p -= (int)BlackPawn;
9267                  p = PieceToNumber((ChessSquare)p);
9268              if(p >= gameInfo.holdingsSize) p = 0;
9269              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9270                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9271              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9272                         board[BOARD_HEIGHT-1-p][1] = 0;
9273         }
9274       }
9275       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9276           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9277         /* [HGM] holdings: Add to holdings, if holdings exist */
9278         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9279                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9280                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9281         }
9282         p = (int) captured;
9283         if (p >= (int) BlackPawn) {
9284           p -= (int)BlackPawn;
9285           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9286                   /* in Shogi restore piece to its original  first */
9287                   captured = (ChessSquare) (DEMOTED captured);
9288                   p = DEMOTED p;
9289           }
9290           p = PieceToNumber((ChessSquare)p);
9291           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9292           board[p][BOARD_WIDTH-2]++;
9293           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9294         } else {
9295           p -= (int)WhitePawn;
9296           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9297                   captured = (ChessSquare) (DEMOTED captured);
9298                   p = DEMOTED p;
9299           }
9300           p = PieceToNumber((ChessSquare)p);
9301           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9302           board[BOARD_HEIGHT-1-p][1]++;
9303           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9304         }
9305       }
9306     } else if (gameInfo.variant == VariantAtomic) {
9307       if (captured != EmptySquare) {
9308         int y, x;
9309         for (y = toY-1; y <= toY+1; y++) {
9310           for (x = toX-1; x <= toX+1; x++) {
9311             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9312                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9313               board[y][x] = EmptySquare;
9314             }
9315           }
9316         }
9317         board[toY][toX] = EmptySquare;
9318       }
9319     }
9320     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9321         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9322     } else
9323     if(promoChar == '+') {
9324         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9325         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9326     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9327         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9328     }
9329     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9330                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9331         // [HGM] superchess: take promotion piece out of holdings
9332         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9333         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9334             if(!--board[k][BOARD_WIDTH-2])
9335                 board[k][BOARD_WIDTH-1] = EmptySquare;
9336         } else {
9337             if(!--board[BOARD_HEIGHT-1-k][1])
9338                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9339         }
9340     }
9341
9342 }
9343
9344 /* Updates forwardMostMove */
9345 void
9346 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9347 {
9348 //    forwardMostMove++; // [HGM] bare: moved downstream
9349
9350     (void) CoordsToAlgebraic(boards[forwardMostMove],
9351                              PosFlags(forwardMostMove),
9352                              fromY, fromX, toY, toX, promoChar,
9353                              parseList[forwardMostMove]);
9354
9355     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9356         int timeLeft; static int lastLoadFlag=0; int king, piece;
9357         piece = boards[forwardMostMove][fromY][fromX];
9358         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9359         if(gameInfo.variant == VariantKnightmate)
9360             king += (int) WhiteUnicorn - (int) WhiteKing;
9361         if(forwardMostMove == 0) {
9362             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9363                 fprintf(serverMoves, "%s;", UserName());
9364             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9365                 fprintf(serverMoves, "%s;", second.tidy);
9366             fprintf(serverMoves, "%s;", first.tidy);
9367             if(gameMode == MachinePlaysWhite)
9368                 fprintf(serverMoves, "%s;", UserName());
9369             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9370                 fprintf(serverMoves, "%s;", second.tidy);
9371         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9372         lastLoadFlag = loadFlag;
9373         // print base move
9374         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9375         // print castling suffix
9376         if( toY == fromY && piece == king ) {
9377             if(toX-fromX > 1)
9378                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9379             if(fromX-toX >1)
9380                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9381         }
9382         // e.p. suffix
9383         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9384              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9385              boards[forwardMostMove][toY][toX] == EmptySquare
9386              && fromX != toX && fromY != toY)
9387                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9388         // promotion suffix
9389         if(promoChar != NULLCHAR)
9390                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9391         if(!loadFlag) {
9392                 char buf[MOVE_LEN*2], *p; int len;
9393             fprintf(serverMoves, "/%d/%d",
9394                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9395             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9396             else                      timeLeft = blackTimeRemaining/1000;
9397             fprintf(serverMoves, "/%d", timeLeft);
9398                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9399                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9400                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9401             fprintf(serverMoves, "/%s", buf);
9402         }
9403         fflush(serverMoves);
9404     }
9405
9406     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9407         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9408       return;
9409     }
9410     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9411     if (commentList[forwardMostMove+1] != NULL) {
9412         free(commentList[forwardMostMove+1]);
9413         commentList[forwardMostMove+1] = NULL;
9414     }
9415     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9416     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9417     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9418     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9419     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9420     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9421     adjustedClock = FALSE;
9422     gameInfo.result = GameUnfinished;
9423     if (gameInfo.resultDetails != NULL) {
9424         free(gameInfo.resultDetails);
9425         gameInfo.resultDetails = NULL;
9426     }
9427     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9428                               moveList[forwardMostMove - 1]);
9429     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9430       case MT_NONE:
9431       case MT_STALEMATE:
9432       default:
9433         break;
9434       case MT_CHECK:
9435         if(gameInfo.variant != VariantShogi)
9436             strcat(parseList[forwardMostMove - 1], "+");
9437         break;
9438       case MT_CHECKMATE:
9439       case MT_STAINMATE:
9440         strcat(parseList[forwardMostMove - 1], "#");
9441         break;
9442     }
9443
9444 }
9445
9446 /* Updates currentMove if not pausing */
9447 void
9448 ShowMove (int fromX, int fromY, int toX, int toY)
9449 {
9450     int instant = (gameMode == PlayFromGameFile) ?
9451         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9452     if(appData.noGUI) return;
9453     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9454         if (!instant) {
9455             if (forwardMostMove == currentMove + 1) {
9456                 AnimateMove(boards[forwardMostMove - 1],
9457                             fromX, fromY, toX, toY);
9458             }
9459             if (appData.highlightLastMove) {
9460                 SetHighlights(fromX, fromY, toX, toY);
9461             }
9462         }
9463         currentMove = forwardMostMove;
9464     }
9465
9466     if (instant) return;
9467
9468     DisplayMove(currentMove - 1);
9469     DrawPosition(FALSE, boards[currentMove]);
9470     DisplayBothClocks();
9471     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9472 }
9473
9474 void
9475 SendEgtPath (ChessProgramState *cps)
9476 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9477         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9478
9479         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9480
9481         while(*p) {
9482             char c, *q = name+1, *r, *s;
9483
9484             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9485             while(*p && *p != ',') *q++ = *p++;
9486             *q++ = ':'; *q = 0;
9487             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9488                 strcmp(name, ",nalimov:") == 0 ) {
9489                 // take nalimov path from the menu-changeable option first, if it is defined
9490               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9491                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9492             } else
9493             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9494                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9495                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9496                 s = r = StrStr(s, ":") + 1; // beginning of path info
9497                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9498                 c = *r; *r = 0;             // temporarily null-terminate path info
9499                     *--q = 0;               // strip of trailig ':' from name
9500                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9501                 *r = c;
9502                 SendToProgram(buf,cps);     // send egtbpath command for this format
9503             }
9504             if(*p == ',') p++; // read away comma to position for next format name
9505         }
9506 }
9507
9508 void
9509 InitChessProgram (ChessProgramState *cps, int setup)
9510 /* setup needed to setup FRC opening position */
9511 {
9512     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9513     if (appData.noChessProgram) return;
9514     hintRequested = FALSE;
9515     bookRequested = FALSE;
9516
9517     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9518     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9519     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9520     if(cps->memSize) { /* [HGM] memory */
9521       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9522         SendToProgram(buf, cps);
9523     }
9524     SendEgtPath(cps); /* [HGM] EGT */
9525     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9526       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9527         SendToProgram(buf, cps);
9528     }
9529
9530     SendToProgram(cps->initString, cps);
9531     if (gameInfo.variant != VariantNormal &&
9532         gameInfo.variant != VariantLoadable
9533         /* [HGM] also send variant if board size non-standard */
9534         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9535                                             ) {
9536       char *v = VariantName(gameInfo.variant);
9537       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9538         /* [HGM] in protocol 1 we have to assume all variants valid */
9539         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9540         DisplayFatalError(buf, 0, 1);
9541         return;
9542       }
9543
9544       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9545       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9546       if( gameInfo.variant == VariantXiangqi )
9547            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9548       if( gameInfo.variant == VariantShogi )
9549            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9550       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9551            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9552       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9553           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9554            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9555       if( gameInfo.variant == VariantCourier )
9556            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9557       if( gameInfo.variant == VariantSuper )
9558            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9559       if( gameInfo.variant == VariantGreat )
9560            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9561       if( gameInfo.variant == VariantSChess )
9562            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9563       if( gameInfo.variant == VariantGrand )
9564            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9565
9566       if(overruled) {
9567         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9568                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9569            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9570            if(StrStr(cps->variants, b) == NULL) {
9571                // specific sized variant not known, check if general sizing allowed
9572                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9573                    if(StrStr(cps->variants, "boardsize") == NULL) {
9574                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9575                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9576                        DisplayFatalError(buf, 0, 1);
9577                        return;
9578                    }
9579                    /* [HGM] here we really should compare with the maximum supported board size */
9580                }
9581            }
9582       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9583       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9584       SendToProgram(buf, cps);
9585     }
9586     currentlyInitializedVariant = gameInfo.variant;
9587
9588     /* [HGM] send opening position in FRC to first engine */
9589     if(setup) {
9590           SendToProgram("force\n", cps);
9591           SendBoard(cps, 0);
9592           /* engine is now in force mode! Set flag to wake it up after first move. */
9593           setboardSpoiledMachineBlack = 1;
9594     }
9595
9596     if (cps->sendICS) {
9597       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9598       SendToProgram(buf, cps);
9599     }
9600     cps->maybeThinking = FALSE;
9601     cps->offeredDraw = 0;
9602     if (!appData.icsActive) {
9603         SendTimeControl(cps, movesPerSession, timeControl,
9604                         timeIncrement, appData.searchDepth,
9605                         searchTime);
9606     }
9607     if (appData.showThinking
9608         // [HGM] thinking: four options require thinking output to be sent
9609         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9610                                 ) {
9611         SendToProgram("post\n", cps);
9612     }
9613     SendToProgram("hard\n", cps);
9614     if (!appData.ponderNextMove) {
9615         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9616            it without being sure what state we are in first.  "hard"
9617            is not a toggle, so that one is OK.
9618          */
9619         SendToProgram("easy\n", cps);
9620     }
9621     if (cps->usePing) {
9622       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9623       SendToProgram(buf, cps);
9624     }
9625     cps->initDone = TRUE;
9626     ClearEngineOutputPane(cps == &second);
9627 }
9628
9629
9630 void
9631 StartChessProgram (ChessProgramState *cps)
9632 {
9633     char buf[MSG_SIZ];
9634     int err;
9635
9636     if (appData.noChessProgram) return;
9637     cps->initDone = FALSE;
9638
9639     if (strcmp(cps->host, "localhost") == 0) {
9640         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9641     } else if (*appData.remoteShell == NULLCHAR) {
9642         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9643     } else {
9644         if (*appData.remoteUser == NULLCHAR) {
9645           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9646                     cps->program);
9647         } else {
9648           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9649                     cps->host, appData.remoteUser, cps->program);
9650         }
9651         err = StartChildProcess(buf, "", &cps->pr);
9652     }
9653
9654     if (err != 0) {
9655       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9656         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9657         if(cps != &first) return;
9658         appData.noChessProgram = TRUE;
9659         ThawUI();
9660         SetNCPMode();
9661 //      DisplayFatalError(buf, err, 1);
9662 //      cps->pr = NoProc;
9663 //      cps->isr = NULL;
9664         return;
9665     }
9666
9667     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9668     if (cps->protocolVersion > 1) {
9669       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9670       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9671       cps->comboCnt = 0;  //                and values of combo boxes
9672       SendToProgram(buf, cps);
9673     } else {
9674       SendToProgram("xboard\n", cps);
9675     }
9676 }
9677
9678 void
9679 TwoMachinesEventIfReady P((void))
9680 {
9681   static int curMess = 0;
9682   if (first.lastPing != first.lastPong) {
9683     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9684     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9685     return;
9686   }
9687   if (second.lastPing != second.lastPong) {
9688     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9689     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9690     return;
9691   }
9692   DisplayMessage("", ""); curMess = 0;
9693   ThawUI();
9694   TwoMachinesEvent();
9695 }
9696
9697 char *
9698 MakeName (char *template)
9699 {
9700     time_t clock;
9701     struct tm *tm;
9702     static char buf[MSG_SIZ];
9703     char *p = buf;
9704     int i;
9705
9706     clock = time((time_t *)NULL);
9707     tm = localtime(&clock);
9708
9709     while(*p++ = *template++) if(p[-1] == '%') {
9710         switch(*template++) {
9711           case 0:   *p = 0; return buf;
9712           case 'Y': i = tm->tm_year+1900; break;
9713           case 'y': i = tm->tm_year-100; break;
9714           case 'M': i = tm->tm_mon+1; break;
9715           case 'd': i = tm->tm_mday; break;
9716           case 'h': i = tm->tm_hour; break;
9717           case 'm': i = tm->tm_min; break;
9718           case 's': i = tm->tm_sec; break;
9719           default:  i = 0;
9720         }
9721         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9722     }
9723     return buf;
9724 }
9725
9726 int
9727 CountPlayers (char *p)
9728 {
9729     int n = 0;
9730     while(p = strchr(p, '\n')) p++, n++; // count participants
9731     return n;
9732 }
9733
9734 FILE *
9735 WriteTourneyFile (char *results, FILE *f)
9736 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9737     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9738     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9739         // create a file with tournament description
9740         fprintf(f, "-participants {%s}\n", appData.participants);
9741         fprintf(f, "-seedBase %d\n", appData.seedBase);
9742         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9743         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9744         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9745         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9746         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9747         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9748         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9749         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9750         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9751         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9752         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9753         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9754         if(searchTime > 0)
9755                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9756         else {
9757                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9758                 fprintf(f, "-tc %s\n", appData.timeControl);
9759                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9760         }
9761         fprintf(f, "-results \"%s\"\n", results);
9762     }
9763     return f;
9764 }
9765
9766 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9767
9768 void
9769 Substitute (char *participants, int expunge)
9770 {
9771     int i, changed, changes=0, nPlayers=0;
9772     char *p, *q, *r, buf[MSG_SIZ];
9773     if(participants == NULL) return;
9774     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9775     r = p = participants; q = appData.participants;
9776     while(*p && *p == *q) {
9777         if(*p == '\n') r = p+1, nPlayers++;
9778         p++; q++;
9779     }
9780     if(*p) { // difference
9781         while(*p && *p++ != '\n');
9782         while(*q && *q++ != '\n');
9783       changed = nPlayers;
9784         changes = 1 + (strcmp(p, q) != 0);
9785     }
9786     if(changes == 1) { // a single engine mnemonic was changed
9787         q = r; while(*q) nPlayers += (*q++ == '\n');
9788         p = buf; while(*r && (*p = *r++) != '\n') p++;
9789         *p = NULLCHAR;
9790         NamesToList(firstChessProgramNames, command, mnemonic, "all");
9791         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9792         if(mnemonic[i]) { // The substitute is valid
9793             FILE *f;
9794             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9795                 flock(fileno(f), LOCK_EX);
9796                 ParseArgsFromFile(f);
9797                 fseek(f, 0, SEEK_SET);
9798                 FREE(appData.participants); appData.participants = participants;
9799                 if(expunge) { // erase results of replaced engine
9800                     int len = strlen(appData.results), w, b, dummy;
9801                     for(i=0; i<len; i++) {
9802                         Pairing(i, nPlayers, &w, &b, &dummy);
9803                         if((w == changed || b == changed) && appData.results[i] == '*') {
9804                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9805                             fclose(f);
9806                             return;
9807                         }
9808                     }
9809                     for(i=0; i<len; i++) {
9810                         Pairing(i, nPlayers, &w, &b, &dummy);
9811                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9812                     }
9813                 }
9814                 WriteTourneyFile(appData.results, f);
9815                 fclose(f); // release lock
9816                 return;
9817             }
9818         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9819     }
9820     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9821     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9822     free(participants);
9823     return;
9824 }
9825
9826 int
9827 CreateTourney (char *name)
9828 {
9829         FILE *f;
9830         if(matchMode && strcmp(name, appData.tourneyFile)) {
9831              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9832         }
9833         if(name[0] == NULLCHAR) {
9834             if(appData.participants[0])
9835                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9836             return 0;
9837         }
9838         f = fopen(name, "r");
9839         if(f) { // file exists
9840             ASSIGN(appData.tourneyFile, name);
9841             ParseArgsFromFile(f); // parse it
9842         } else {
9843             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9844             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9845                 DisplayError(_("Not enough participants"), 0);
9846                 return 0;
9847             }
9848             ASSIGN(appData.tourneyFile, name);
9849             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9850             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9851         }
9852         fclose(f);
9853         appData.noChessProgram = FALSE;
9854         appData.clockMode = TRUE;
9855         SetGNUMode();
9856         return 1;
9857 }
9858
9859 int
9860 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
9861 {
9862     char buf[MSG_SIZ], *p, *q;
9863     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
9864     skip = !all && group[0]; // if group requested, we start in skip mode
9865     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
9866         p = names; q = buf; header = 0;
9867         while(*p && *p != '\n') *q++ = *p++;
9868         *q = 0;
9869         if(*p == '\n') p++;
9870         if(buf[0] == '#') {
9871             if(strstr(buf, "# end") == buf) { depth--; continue; } // leave group, and suppress printing label
9872             depth++; // we must be entering a new group
9873             if(all) continue; // suppress printing group headers when complete list requested
9874             header = 1;
9875             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
9876         }
9877         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
9878         if(engineList[i]) free(engineList[i]);
9879         engineList[i] = strdup(buf);
9880         if(buf[0] != '#') TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
9881         if(engineMnemonic[i]) free(engineMnemonic[i]);
9882         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9883             strcat(buf, " (");
9884             sscanf(q + 8, "%s", buf + strlen(buf));
9885             strcat(buf, ")");
9886         }
9887         engineMnemonic[i] = strdup(buf);
9888         i++;
9889     }
9890     engineList[i] = engineMnemonic[i] = NULL;
9891     return i;
9892 }
9893
9894 // following implemented as macro to avoid type limitations
9895 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9896
9897 void
9898 SwapEngines (int n)
9899 {   // swap settings for first engine and other engine (so far only some selected options)
9900     int h;
9901     char *p;
9902     if(n == 0) return;
9903     SWAP(directory, p)
9904     SWAP(chessProgram, p)
9905     SWAP(isUCI, h)
9906     SWAP(hasOwnBookUCI, h)
9907     SWAP(protocolVersion, h)
9908     SWAP(reuse, h)
9909     SWAP(scoreIsAbsolute, h)
9910     SWAP(timeOdds, h)
9911     SWAP(logo, p)
9912     SWAP(pgnName, p)
9913     SWAP(pvSAN, h)
9914     SWAP(engOptions, p)
9915 }
9916
9917 int
9918 SetPlayer (int player, char *p)
9919 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9920     int i;
9921     char buf[MSG_SIZ], *engineName;
9922     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9923     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9924     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9925     if(mnemonic[i]) {
9926         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9927         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9928         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
9929         ParseArgsFromString(buf);
9930     }
9931     free(engineName);
9932     return i;
9933 }
9934
9935 char *recentEngines;
9936
9937 void
9938 RecentEngineEvent (int nr)
9939 {
9940     int n;
9941 //    SwapEngines(1); // bump first to second
9942 //    ReplaceEngine(&second, 1); // and load it there
9943     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
9944     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
9945     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
9946         ReplaceEngine(&first, 0);
9947         FloatToFront(&appData.recentEngineList, command[n]);
9948     }
9949 }
9950
9951 int
9952 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9953 {   // determine players from game number
9954     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9955
9956     if(appData.tourneyType == 0) {
9957         roundsPerCycle = (nPlayers - 1) | 1;
9958         pairingsPerRound = nPlayers / 2;
9959     } else if(appData.tourneyType > 0) {
9960         roundsPerCycle = nPlayers - appData.tourneyType;
9961         pairingsPerRound = appData.tourneyType;
9962     }
9963     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9964     gamesPerCycle = gamesPerRound * roundsPerCycle;
9965     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9966     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9967     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9968     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9969     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9970     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9971
9972     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9973     if(appData.roundSync) *syncInterval = gamesPerRound;
9974
9975     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9976
9977     if(appData.tourneyType == 0) {
9978         if(curPairing == (nPlayers-1)/2 ) {
9979             *whitePlayer = curRound;
9980             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9981         } else {
9982             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9983             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9984             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9985             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9986         }
9987     } else if(appData.tourneyType > 1) {
9988         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
9989         *whitePlayer = curRound + appData.tourneyType;
9990     } else if(appData.tourneyType > 0) {
9991         *whitePlayer = curPairing;
9992         *blackPlayer = curRound + appData.tourneyType;
9993     }
9994
9995     // take care of white/black alternation per round. 
9996     // For cycles and games this is already taken care of by default, derived from matchGame!
9997     return curRound & 1;
9998 }
9999
10000 int
10001 NextTourneyGame (int nr, int *swapColors)
10002 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10003     char *p, *q;
10004     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10005     FILE *tf;
10006     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10007     tf = fopen(appData.tourneyFile, "r");
10008     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10009     ParseArgsFromFile(tf); fclose(tf);
10010     InitTimeControls(); // TC might be altered from tourney file
10011
10012     nPlayers = CountPlayers(appData.participants); // count participants
10013     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10014     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10015
10016     if(syncInterval) {
10017         p = q = appData.results;
10018         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10019         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10020             DisplayMessage(_("Waiting for other game(s)"),"");
10021             waitingForGame = TRUE;
10022             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10023             return 0;
10024         }
10025         waitingForGame = FALSE;
10026     }
10027
10028     if(appData.tourneyType < 0) {
10029         if(nr>=0 && !pairingReceived) {
10030             char buf[1<<16];
10031             if(pairing.pr == NoProc) {
10032                 if(!appData.pairingEngine[0]) {
10033                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10034                     return 0;
10035                 }
10036                 StartChessProgram(&pairing); // starts the pairing engine
10037             }
10038             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10039             SendToProgram(buf, &pairing);
10040             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10041             SendToProgram(buf, &pairing);
10042             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10043         }
10044         pairingReceived = 0;                              // ... so we continue here 
10045         *swapColors = 0;
10046         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10047         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10048         matchGame = 1; roundNr = nr / syncInterval + 1;
10049     }
10050
10051     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10052
10053     // redefine engines, engine dir, etc.
10054     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10055     if(first.pr == NoProc) {
10056       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10057       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10058     }
10059     if(second.pr == NoProc) {
10060       SwapEngines(1);
10061       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10062       SwapEngines(1);         // and make that valid for second engine by swapping
10063       InitEngine(&second, 1);
10064     }
10065     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10066     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10067     return 1;
10068 }
10069
10070 void
10071 NextMatchGame ()
10072 {   // performs game initialization that does not invoke engines, and then tries to start the game
10073     int res, firstWhite, swapColors = 0;
10074     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10075     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10076     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10077     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10078     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10079     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10080     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10081     Reset(FALSE, first.pr != NoProc);
10082     res = LoadGameOrPosition(matchGame); // setup game
10083     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10084     if(!res) return; // abort when bad game/pos file
10085     TwoMachinesEvent();
10086 }
10087
10088 void
10089 UserAdjudicationEvent (int result)
10090 {
10091     ChessMove gameResult = GameIsDrawn;
10092
10093     if( result > 0 ) {
10094         gameResult = WhiteWins;
10095     }
10096     else if( result < 0 ) {
10097         gameResult = BlackWins;
10098     }
10099
10100     if( gameMode == TwoMachinesPlay ) {
10101         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10102     }
10103 }
10104
10105
10106 // [HGM] save: calculate checksum of game to make games easily identifiable
10107 int
10108 StringCheckSum (char *s)
10109 {
10110         int i = 0;
10111         if(s==NULL) return 0;
10112         while(*s) i = i*259 + *s++;
10113         return i;
10114 }
10115
10116 int
10117 GameCheckSum ()
10118 {
10119         int i, sum=0;
10120         for(i=backwardMostMove; i<forwardMostMove; i++) {
10121                 sum += pvInfoList[i].depth;
10122                 sum += StringCheckSum(parseList[i]);
10123                 sum += StringCheckSum(commentList[i]);
10124                 sum *= 261;
10125         }
10126         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10127         return sum + StringCheckSum(commentList[i]);
10128 } // end of save patch
10129
10130 void
10131 GameEnds (ChessMove result, char *resultDetails, int whosays)
10132 {
10133     GameMode nextGameMode;
10134     int isIcsGame;
10135     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10136
10137     if(endingGame) return; /* [HGM] crash: forbid recursion */
10138     endingGame = 1;
10139     if(twoBoards) { // [HGM] dual: switch back to one board
10140         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10141         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10142     }
10143     if (appData.debugMode) {
10144       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10145               result, resultDetails ? resultDetails : "(null)", whosays);
10146     }
10147
10148     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10149
10150     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10151         /* If we are playing on ICS, the server decides when the
10152            game is over, but the engine can offer to draw, claim
10153            a draw, or resign.
10154          */
10155 #if ZIPPY
10156         if (appData.zippyPlay && first.initDone) {
10157             if (result == GameIsDrawn) {
10158                 /* In case draw still needs to be claimed */
10159                 SendToICS(ics_prefix);
10160                 SendToICS("draw\n");
10161             } else if (StrCaseStr(resultDetails, "resign")) {
10162                 SendToICS(ics_prefix);
10163                 SendToICS("resign\n");
10164             }
10165         }
10166 #endif
10167         endingGame = 0; /* [HGM] crash */
10168         return;
10169     }
10170
10171     /* If we're loading the game from a file, stop */
10172     if (whosays == GE_FILE) {
10173       (void) StopLoadGameTimer();
10174       gameFileFP = NULL;
10175     }
10176
10177     /* Cancel draw offers */
10178     first.offeredDraw = second.offeredDraw = 0;
10179
10180     /* If this is an ICS game, only ICS can really say it's done;
10181        if not, anyone can. */
10182     isIcsGame = (gameMode == IcsPlayingWhite ||
10183                  gameMode == IcsPlayingBlack ||
10184                  gameMode == IcsObserving    ||
10185                  gameMode == IcsExamining);
10186
10187     if (!isIcsGame || whosays == GE_ICS) {
10188         /* OK -- not an ICS game, or ICS said it was done */
10189         StopClocks();
10190         if (!isIcsGame && !appData.noChessProgram)
10191           SetUserThinkingEnables();
10192
10193         /* [HGM] if a machine claims the game end we verify this claim */
10194         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10195             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10196                 char claimer;
10197                 ChessMove trueResult = (ChessMove) -1;
10198
10199                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10200                                             first.twoMachinesColor[0] :
10201                                             second.twoMachinesColor[0] ;
10202
10203                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10204                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10205                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10206                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10207                 } else
10208                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10209                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10210                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10211                 } else
10212                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10213                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10214                 }
10215
10216                 // now verify win claims, but not in drop games, as we don't understand those yet
10217                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10218                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10219                     (result == WhiteWins && claimer == 'w' ||
10220                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10221                       if (appData.debugMode) {
10222                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10223                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10224                       }
10225                       if(result != trueResult) {
10226                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10227                               result = claimer == 'w' ? BlackWins : WhiteWins;
10228                               resultDetails = buf;
10229                       }
10230                 } else
10231                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10232                     && (forwardMostMove <= backwardMostMove ||
10233                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10234                         (claimer=='b')==(forwardMostMove&1))
10235                                                                                   ) {
10236                       /* [HGM] verify: draws that were not flagged are false claims */
10237                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10238                       result = claimer == 'w' ? BlackWins : WhiteWins;
10239                       resultDetails = buf;
10240                 }
10241                 /* (Claiming a loss is accepted no questions asked!) */
10242             }
10243             /* [HGM] bare: don't allow bare King to win */
10244             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10245                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10246                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10247                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10248                && result != GameIsDrawn)
10249             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10250                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10251                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10252                         if(p >= 0 && p <= (int)WhiteKing) k++;
10253                 }
10254                 if (appData.debugMode) {
10255                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10256                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10257                 }
10258                 if(k <= 1) {
10259                         result = GameIsDrawn;
10260                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10261                         resultDetails = buf;
10262                 }
10263             }
10264         }
10265
10266
10267         if(serverMoves != NULL && !loadFlag) { char c = '=';
10268             if(result==WhiteWins) c = '+';
10269             if(result==BlackWins) c = '-';
10270             if(resultDetails != NULL)
10271                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10272         }
10273         if (resultDetails != NULL) {
10274             gameInfo.result = result;
10275             gameInfo.resultDetails = StrSave(resultDetails);
10276
10277             /* display last move only if game was not loaded from file */
10278             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10279                 DisplayMove(currentMove - 1);
10280
10281             if (forwardMostMove != 0) {
10282                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10283                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10284                                                                 ) {
10285                     if (*appData.saveGameFile != NULLCHAR) {
10286                         SaveGameToFile(appData.saveGameFile, TRUE);
10287                     } else if (appData.autoSaveGames) {
10288                         AutoSaveGame();
10289                     }
10290                     if (*appData.savePositionFile != NULLCHAR) {
10291                         SavePositionToFile(appData.savePositionFile);
10292                     }
10293                 }
10294             }
10295
10296             /* Tell program how game ended in case it is learning */
10297             /* [HGM] Moved this to after saving the PGN, just in case */
10298             /* engine died and we got here through time loss. In that */
10299             /* case we will get a fatal error writing the pipe, which */
10300             /* would otherwise lose us the PGN.                       */
10301             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10302             /* output during GameEnds should never be fatal anymore   */
10303             if (gameMode == MachinePlaysWhite ||
10304                 gameMode == MachinePlaysBlack ||
10305                 gameMode == TwoMachinesPlay ||
10306                 gameMode == IcsPlayingWhite ||
10307                 gameMode == IcsPlayingBlack ||
10308                 gameMode == BeginningOfGame) {
10309                 char buf[MSG_SIZ];
10310                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10311                         resultDetails);
10312                 if (first.pr != NoProc) {
10313                     SendToProgram(buf, &first);
10314                 }
10315                 if (second.pr != NoProc &&
10316                     gameMode == TwoMachinesPlay) {
10317                     SendToProgram(buf, &second);
10318                 }
10319             }
10320         }
10321
10322         if (appData.icsActive) {
10323             if (appData.quietPlay &&
10324                 (gameMode == IcsPlayingWhite ||
10325                  gameMode == IcsPlayingBlack)) {
10326                 SendToICS(ics_prefix);
10327                 SendToICS("set shout 1\n");
10328             }
10329             nextGameMode = IcsIdle;
10330             ics_user_moved = FALSE;
10331             /* clean up premove.  It's ugly when the game has ended and the
10332              * premove highlights are still on the board.
10333              */
10334             if (gotPremove) {
10335               gotPremove = FALSE;
10336               ClearPremoveHighlights();
10337               DrawPosition(FALSE, boards[currentMove]);
10338             }
10339             if (whosays == GE_ICS) {
10340                 switch (result) {
10341                 case WhiteWins:
10342                     if (gameMode == IcsPlayingWhite)
10343                         PlayIcsWinSound();
10344                     else if(gameMode == IcsPlayingBlack)
10345                         PlayIcsLossSound();
10346                     break;
10347                 case BlackWins:
10348                     if (gameMode == IcsPlayingBlack)
10349                         PlayIcsWinSound();
10350                     else if(gameMode == IcsPlayingWhite)
10351                         PlayIcsLossSound();
10352                     break;
10353                 case GameIsDrawn:
10354                     PlayIcsDrawSound();
10355                     break;
10356                 default:
10357                     PlayIcsUnfinishedSound();
10358                 }
10359             }
10360         } else if (gameMode == EditGame ||
10361                    gameMode == PlayFromGameFile ||
10362                    gameMode == AnalyzeMode ||
10363                    gameMode == AnalyzeFile) {
10364             nextGameMode = gameMode;
10365         } else {
10366             nextGameMode = EndOfGame;
10367         }
10368         pausing = FALSE;
10369         ModeHighlight();
10370     } else {
10371         nextGameMode = gameMode;
10372     }
10373
10374     if (appData.noChessProgram) {
10375         gameMode = nextGameMode;
10376         ModeHighlight();
10377         endingGame = 0; /* [HGM] crash */
10378         return;
10379     }
10380
10381     if (first.reuse) {
10382         /* Put first chess program into idle state */
10383         if (first.pr != NoProc &&
10384             (gameMode == MachinePlaysWhite ||
10385              gameMode == MachinePlaysBlack ||
10386              gameMode == TwoMachinesPlay ||
10387              gameMode == IcsPlayingWhite ||
10388              gameMode == IcsPlayingBlack ||
10389              gameMode == BeginningOfGame)) {
10390             SendToProgram("force\n", &first);
10391             if (first.usePing) {
10392               char buf[MSG_SIZ];
10393               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10394               SendToProgram(buf, &first);
10395             }
10396         }
10397     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10398         /* Kill off first chess program */
10399         if (first.isr != NULL)
10400           RemoveInputSource(first.isr);
10401         first.isr = NULL;
10402
10403         if (first.pr != NoProc) {
10404             ExitAnalyzeMode();
10405             DoSleep( appData.delayBeforeQuit );
10406             SendToProgram("quit\n", &first);
10407             DoSleep( appData.delayAfterQuit );
10408             DestroyChildProcess(first.pr, first.useSigterm);
10409         }
10410         first.pr = NoProc;
10411     }
10412     if (second.reuse) {
10413         /* Put second chess program into idle state */
10414         if (second.pr != NoProc &&
10415             gameMode == TwoMachinesPlay) {
10416             SendToProgram("force\n", &second);
10417             if (second.usePing) {
10418               char buf[MSG_SIZ];
10419               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10420               SendToProgram(buf, &second);
10421             }
10422         }
10423     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10424         /* Kill off second chess program */
10425         if (second.isr != NULL)
10426           RemoveInputSource(second.isr);
10427         second.isr = NULL;
10428
10429         if (second.pr != NoProc) {
10430             DoSleep( appData.delayBeforeQuit );
10431             SendToProgram("quit\n", &second);
10432             DoSleep( appData.delayAfterQuit );
10433             DestroyChildProcess(second.pr, second.useSigterm);
10434         }
10435         second.pr = NoProc;
10436     }
10437
10438     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10439         char resChar = '=';
10440         switch (result) {
10441         case WhiteWins:
10442           resChar = '+';
10443           if (first.twoMachinesColor[0] == 'w') {
10444             first.matchWins++;
10445           } else {
10446             second.matchWins++;
10447           }
10448           break;
10449         case BlackWins:
10450           resChar = '-';
10451           if (first.twoMachinesColor[0] == 'b') {
10452             first.matchWins++;
10453           } else {
10454             second.matchWins++;
10455           }
10456           break;
10457         case GameUnfinished:
10458           resChar = ' ';
10459         default:
10460           break;
10461         }
10462
10463         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10464         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10465             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10466             ReserveGame(nextGame, resChar); // sets nextGame
10467             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10468             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10469         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10470
10471         if (nextGame <= appData.matchGames && !abortMatch) {
10472             gameMode = nextGameMode;
10473             matchGame = nextGame; // this will be overruled in tourney mode!
10474             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10475             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10476             endingGame = 0; /* [HGM] crash */
10477             return;
10478         } else {
10479             gameMode = nextGameMode;
10480             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10481                      first.tidy, second.tidy,
10482                      first.matchWins, second.matchWins,
10483                      appData.matchGames - (first.matchWins + second.matchWins));
10484             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10485             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10486             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10487             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10488                 first.twoMachinesColor = "black\n";
10489                 second.twoMachinesColor = "white\n";
10490             } else {
10491                 first.twoMachinesColor = "white\n";
10492                 second.twoMachinesColor = "black\n";
10493             }
10494         }
10495     }
10496     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10497         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10498       ExitAnalyzeMode();
10499     gameMode = nextGameMode;
10500     ModeHighlight();
10501     endingGame = 0;  /* [HGM] crash */
10502     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10503         if(matchMode == TRUE) { // match through command line: exit with or without popup
10504             if(ranking) {
10505                 ToNrEvent(forwardMostMove);
10506                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10507                 else ExitEvent(0);
10508             } else DisplayFatalError(buf, 0, 0);
10509         } else { // match through menu; just stop, with or without popup
10510             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10511             ModeHighlight();
10512             if(ranking){
10513                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10514             } else DisplayNote(buf);
10515       }
10516       if(ranking) free(ranking);
10517     }
10518 }
10519
10520 /* Assumes program was just initialized (initString sent).
10521    Leaves program in force mode. */
10522 void
10523 FeedMovesToProgram (ChessProgramState *cps, int upto)
10524 {
10525     int i;
10526
10527     if (appData.debugMode)
10528       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10529               startedFromSetupPosition ? "position and " : "",
10530               backwardMostMove, upto, cps->which);
10531     if(currentlyInitializedVariant != gameInfo.variant) {
10532       char buf[MSG_SIZ];
10533         // [HGM] variantswitch: make engine aware of new variant
10534         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10535                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10536         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10537         SendToProgram(buf, cps);
10538         currentlyInitializedVariant = gameInfo.variant;
10539     }
10540     SendToProgram("force\n", cps);
10541     if (startedFromSetupPosition) {
10542         SendBoard(cps, backwardMostMove);
10543     if (appData.debugMode) {
10544         fprintf(debugFP, "feedMoves\n");
10545     }
10546     }
10547     for (i = backwardMostMove; i < upto; i++) {
10548         SendMoveToProgram(i, cps);
10549     }
10550 }
10551
10552
10553 int
10554 ResurrectChessProgram ()
10555 {
10556      /* The chess program may have exited.
10557         If so, restart it and feed it all the moves made so far. */
10558     static int doInit = 0;
10559
10560     if (appData.noChessProgram) return 1;
10561
10562     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10563         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10564         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10565         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10566     } else {
10567         if (first.pr != NoProc) return 1;
10568         StartChessProgram(&first);
10569     }
10570     InitChessProgram(&first, FALSE);
10571     FeedMovesToProgram(&first, currentMove);
10572
10573     if (!first.sendTime) {
10574         /* can't tell gnuchess what its clock should read,
10575            so we bow to its notion. */
10576         ResetClocks();
10577         timeRemaining[0][currentMove] = whiteTimeRemaining;
10578         timeRemaining[1][currentMove] = blackTimeRemaining;
10579     }
10580
10581     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10582                 appData.icsEngineAnalyze) && first.analysisSupport) {
10583       SendToProgram("analyze\n", &first);
10584       first.analyzing = TRUE;
10585     }
10586     return 1;
10587 }
10588
10589 /*
10590  * Button procedures
10591  */
10592 void
10593 Reset (int redraw, int init)
10594 {
10595     int i;
10596
10597     if (appData.debugMode) {
10598         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10599                 redraw, init, gameMode);
10600     }
10601     CleanupTail(); // [HGM] vari: delete any stored variations
10602     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10603     pausing = pauseExamInvalid = FALSE;
10604     startedFromSetupPosition = blackPlaysFirst = FALSE;
10605     firstMove = TRUE;
10606     whiteFlag = blackFlag = FALSE;
10607     userOfferedDraw = FALSE;
10608     hintRequested = bookRequested = FALSE;
10609     first.maybeThinking = FALSE;
10610     second.maybeThinking = FALSE;
10611     first.bookSuspend = FALSE; // [HGM] book
10612     second.bookSuspend = FALSE;
10613     thinkOutput[0] = NULLCHAR;
10614     lastHint[0] = NULLCHAR;
10615     ClearGameInfo(&gameInfo);
10616     gameInfo.variant = StringToVariant(appData.variant);
10617     ics_user_moved = ics_clock_paused = FALSE;
10618     ics_getting_history = H_FALSE;
10619     ics_gamenum = -1;
10620     white_holding[0] = black_holding[0] = NULLCHAR;
10621     ClearProgramStats();
10622     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10623
10624     ResetFrontEnd();
10625     ClearHighlights();
10626     flipView = appData.flipView;
10627     ClearPremoveHighlights();
10628     gotPremove = FALSE;
10629     alarmSounded = FALSE;
10630
10631     GameEnds(EndOfFile, NULL, GE_PLAYER);
10632     if(appData.serverMovesName != NULL) {
10633         /* [HGM] prepare to make moves file for broadcasting */
10634         clock_t t = clock();
10635         if(serverMoves != NULL) fclose(serverMoves);
10636         serverMoves = fopen(appData.serverMovesName, "r");
10637         if(serverMoves != NULL) {
10638             fclose(serverMoves);
10639             /* delay 15 sec before overwriting, so all clients can see end */
10640             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10641         }
10642         serverMoves = fopen(appData.serverMovesName, "w");
10643     }
10644
10645     ExitAnalyzeMode();
10646     gameMode = BeginningOfGame;
10647     ModeHighlight();
10648     if(appData.icsActive) gameInfo.variant = VariantNormal;
10649     currentMove = forwardMostMove = backwardMostMove = 0;
10650     MarkTargetSquares(1);
10651     InitPosition(redraw);
10652     for (i = 0; i < MAX_MOVES; i++) {
10653         if (commentList[i] != NULL) {
10654             free(commentList[i]);
10655             commentList[i] = NULL;
10656         }
10657     }
10658     ResetClocks();
10659     timeRemaining[0][0] = whiteTimeRemaining;
10660     timeRemaining[1][0] = blackTimeRemaining;
10661
10662     if (first.pr == NoProc) {
10663         StartChessProgram(&first);
10664     }
10665     if (init) {
10666             InitChessProgram(&first, startedFromSetupPosition);
10667     }
10668     DisplayTitle("");
10669     DisplayMessage("", "");
10670     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10671     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10672 }
10673
10674 void
10675 AutoPlayGameLoop ()
10676 {
10677     for (;;) {
10678         if (!AutoPlayOneMove())
10679           return;
10680         if (matchMode || appData.timeDelay == 0)
10681           continue;
10682         if (appData.timeDelay < 0)
10683           return;
10684         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10685         break;
10686     }
10687 }
10688
10689
10690 int
10691 AutoPlayOneMove ()
10692 {
10693     int fromX, fromY, toX, toY;
10694
10695     if (appData.debugMode) {
10696       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10697     }
10698
10699     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10700       return FALSE;
10701
10702     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10703       pvInfoList[currentMove].depth = programStats.depth;
10704       pvInfoList[currentMove].score = programStats.score;
10705       pvInfoList[currentMove].time  = 0;
10706       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10707     }
10708
10709     if (currentMove >= forwardMostMove) {
10710       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10711 //      gameMode = EndOfGame;
10712 //      ModeHighlight();
10713
10714       /* [AS] Clear current move marker at the end of a game */
10715       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10716
10717       return FALSE;
10718     }
10719
10720     toX = moveList[currentMove][2] - AAA;
10721     toY = moveList[currentMove][3] - ONE;
10722
10723     if (moveList[currentMove][1] == '@') {
10724         if (appData.highlightLastMove) {
10725             SetHighlights(-1, -1, toX, toY);
10726         }
10727     } else {
10728         fromX = moveList[currentMove][0] - AAA;
10729         fromY = moveList[currentMove][1] - ONE;
10730
10731         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10732
10733         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10734
10735         if (appData.highlightLastMove) {
10736             SetHighlights(fromX, fromY, toX, toY);
10737         }
10738     }
10739     DisplayMove(currentMove);
10740     SendMoveToProgram(currentMove++, &first);
10741     DisplayBothClocks();
10742     DrawPosition(FALSE, boards[currentMove]);
10743     // [HGM] PV info: always display, routine tests if empty
10744     DisplayComment(currentMove - 1, commentList[currentMove]);
10745     return TRUE;
10746 }
10747
10748
10749 int
10750 LoadGameOneMove (ChessMove readAhead)
10751 {
10752     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10753     char promoChar = NULLCHAR;
10754     ChessMove moveType;
10755     char move[MSG_SIZ];
10756     char *p, *q;
10757
10758     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10759         gameMode != AnalyzeMode && gameMode != Training) {
10760         gameFileFP = NULL;
10761         return FALSE;
10762     }
10763
10764     yyboardindex = forwardMostMove;
10765     if (readAhead != EndOfFile) {
10766       moveType = readAhead;
10767     } else {
10768       if (gameFileFP == NULL)
10769           return FALSE;
10770       moveType = (ChessMove) Myylex();
10771     }
10772
10773     done = FALSE;
10774     switch (moveType) {
10775       case Comment:
10776         if (appData.debugMode)
10777           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10778         p = yy_text;
10779
10780         /* append the comment but don't display it */
10781         AppendComment(currentMove, p, FALSE);
10782         return TRUE;
10783
10784       case WhiteCapturesEnPassant:
10785       case BlackCapturesEnPassant:
10786       case WhitePromotion:
10787       case BlackPromotion:
10788       case WhiteNonPromotion:
10789       case BlackNonPromotion:
10790       case NormalMove:
10791       case WhiteKingSideCastle:
10792       case WhiteQueenSideCastle:
10793       case BlackKingSideCastle:
10794       case BlackQueenSideCastle:
10795       case WhiteKingSideCastleWild:
10796       case WhiteQueenSideCastleWild:
10797       case BlackKingSideCastleWild:
10798       case BlackQueenSideCastleWild:
10799       /* PUSH Fabien */
10800       case WhiteHSideCastleFR:
10801       case WhiteASideCastleFR:
10802       case BlackHSideCastleFR:
10803       case BlackASideCastleFR:
10804       /* POP Fabien */
10805         if (appData.debugMode)
10806           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10807         fromX = currentMoveString[0] - AAA;
10808         fromY = currentMoveString[1] - ONE;
10809         toX = currentMoveString[2] - AAA;
10810         toY = currentMoveString[3] - ONE;
10811         promoChar = currentMoveString[4];
10812         break;
10813
10814       case WhiteDrop:
10815       case BlackDrop:
10816         if (appData.debugMode)
10817           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10818         fromX = moveType == WhiteDrop ?
10819           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10820         (int) CharToPiece(ToLower(currentMoveString[0]));
10821         fromY = DROP_RANK;
10822         toX = currentMoveString[2] - AAA;
10823         toY = currentMoveString[3] - ONE;
10824         break;
10825
10826       case WhiteWins:
10827       case BlackWins:
10828       case GameIsDrawn:
10829       case GameUnfinished:
10830         if (appData.debugMode)
10831           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10832         p = strchr(yy_text, '{');
10833         if (p == NULL) p = strchr(yy_text, '(');
10834         if (p == NULL) {
10835             p = yy_text;
10836             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10837         } else {
10838             q = strchr(p, *p == '{' ? '}' : ')');
10839             if (q != NULL) *q = NULLCHAR;
10840             p++;
10841         }
10842         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10843         GameEnds(moveType, p, GE_FILE);
10844         done = TRUE;
10845         if (cmailMsgLoaded) {
10846             ClearHighlights();
10847             flipView = WhiteOnMove(currentMove);
10848             if (moveType == GameUnfinished) flipView = !flipView;
10849             if (appData.debugMode)
10850               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10851         }
10852         break;
10853
10854       case EndOfFile:
10855         if (appData.debugMode)
10856           fprintf(debugFP, "Parser hit end of file\n");
10857         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10858           case MT_NONE:
10859           case MT_CHECK:
10860             break;
10861           case MT_CHECKMATE:
10862           case MT_STAINMATE:
10863             if (WhiteOnMove(currentMove)) {
10864                 GameEnds(BlackWins, "Black mates", GE_FILE);
10865             } else {
10866                 GameEnds(WhiteWins, "White mates", GE_FILE);
10867             }
10868             break;
10869           case MT_STALEMATE:
10870             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10871             break;
10872         }
10873         done = TRUE;
10874         break;
10875
10876       case MoveNumberOne:
10877         if (lastLoadGameStart == GNUChessGame) {
10878             /* GNUChessGames have numbers, but they aren't move numbers */
10879             if (appData.debugMode)
10880               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10881                       yy_text, (int) moveType);
10882             return LoadGameOneMove(EndOfFile); /* tail recursion */
10883         }
10884         /* else fall thru */
10885
10886       case XBoardGame:
10887       case GNUChessGame:
10888       case PGNTag:
10889         /* Reached start of next game in file */
10890         if (appData.debugMode)
10891           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10892         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10893           case MT_NONE:
10894           case MT_CHECK:
10895             break;
10896           case MT_CHECKMATE:
10897           case MT_STAINMATE:
10898             if (WhiteOnMove(currentMove)) {
10899                 GameEnds(BlackWins, "Black mates", GE_FILE);
10900             } else {
10901                 GameEnds(WhiteWins, "White mates", GE_FILE);
10902             }
10903             break;
10904           case MT_STALEMATE:
10905             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10906             break;
10907         }
10908         done = TRUE;
10909         break;
10910
10911       case PositionDiagram:     /* should not happen; ignore */
10912       case ElapsedTime:         /* ignore */
10913       case NAG:                 /* ignore */
10914         if (appData.debugMode)
10915           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10916                   yy_text, (int) moveType);
10917         return LoadGameOneMove(EndOfFile); /* tail recursion */
10918
10919       case IllegalMove:
10920         if (appData.testLegality) {
10921             if (appData.debugMode)
10922               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10923             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10924                     (forwardMostMove / 2) + 1,
10925                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10926             DisplayError(move, 0);
10927             done = TRUE;
10928         } else {
10929             if (appData.debugMode)
10930               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10931                       yy_text, currentMoveString);
10932             fromX = currentMoveString[0] - AAA;
10933             fromY = currentMoveString[1] - ONE;
10934             toX = currentMoveString[2] - AAA;
10935             toY = currentMoveString[3] - ONE;
10936             promoChar = currentMoveString[4];
10937         }
10938         break;
10939
10940       case AmbiguousMove:
10941         if (appData.debugMode)
10942           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10943         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10944                 (forwardMostMove / 2) + 1,
10945                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10946         DisplayError(move, 0);
10947         done = TRUE;
10948         break;
10949
10950       default:
10951       case ImpossibleMove:
10952         if (appData.debugMode)
10953           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10954         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10955                 (forwardMostMove / 2) + 1,
10956                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10957         DisplayError(move, 0);
10958         done = TRUE;
10959         break;
10960     }
10961
10962     if (done) {
10963         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10964             DrawPosition(FALSE, boards[currentMove]);
10965             DisplayBothClocks();
10966             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10967               DisplayComment(currentMove - 1, commentList[currentMove]);
10968         }
10969         (void) StopLoadGameTimer();
10970         gameFileFP = NULL;
10971         cmailOldMove = forwardMostMove;
10972         return FALSE;
10973     } else {
10974         /* currentMoveString is set as a side-effect of yylex */
10975
10976         thinkOutput[0] = NULLCHAR;
10977         MakeMove(fromX, fromY, toX, toY, promoChar);
10978         currentMove = forwardMostMove;
10979         return TRUE;
10980     }
10981 }
10982
10983 /* Load the nth game from the given file */
10984 int
10985 LoadGameFromFile (char *filename, int n, char *title, int useList)
10986 {
10987     FILE *f;
10988     char buf[MSG_SIZ];
10989
10990     if (strcmp(filename, "-") == 0) {
10991         f = stdin;
10992         title = "stdin";
10993     } else {
10994         f = fopen(filename, "rb");
10995         if (f == NULL) {
10996           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10997             DisplayError(buf, errno);
10998             return FALSE;
10999         }
11000     }
11001     if (fseek(f, 0, 0) == -1) {
11002         /* f is not seekable; probably a pipe */
11003         useList = FALSE;
11004     }
11005     if (useList && n == 0) {
11006         int error = GameListBuild(f);
11007         if (error) {
11008             DisplayError(_("Cannot build game list"), error);
11009         } else if (!ListEmpty(&gameList) &&
11010                    ((ListGame *) gameList.tailPred)->number > 1) {
11011             GameListPopUp(f, title);
11012             return TRUE;
11013         }
11014         GameListDestroy();
11015         n = 1;
11016     }
11017     if (n == 0) n = 1;
11018     return LoadGame(f, n, title, FALSE);
11019 }
11020
11021
11022 void
11023 MakeRegisteredMove ()
11024 {
11025     int fromX, fromY, toX, toY;
11026     char promoChar;
11027     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11028         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11029           case CMAIL_MOVE:
11030           case CMAIL_DRAW:
11031             if (appData.debugMode)
11032               fprintf(debugFP, "Restoring %s for game %d\n",
11033                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11034
11035             thinkOutput[0] = NULLCHAR;
11036             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11037             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11038             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11039             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11040             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11041             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11042             MakeMove(fromX, fromY, toX, toY, promoChar);
11043             ShowMove(fromX, fromY, toX, toY);
11044
11045             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11046               case MT_NONE:
11047               case MT_CHECK:
11048                 break;
11049
11050               case MT_CHECKMATE:
11051               case MT_STAINMATE:
11052                 if (WhiteOnMove(currentMove)) {
11053                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11054                 } else {
11055                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11056                 }
11057                 break;
11058
11059               case MT_STALEMATE:
11060                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11061                 break;
11062             }
11063
11064             break;
11065
11066           case CMAIL_RESIGN:
11067             if (WhiteOnMove(currentMove)) {
11068                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11069             } else {
11070                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11071             }
11072             break;
11073
11074           case CMAIL_ACCEPT:
11075             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11076             break;
11077
11078           default:
11079             break;
11080         }
11081     }
11082
11083     return;
11084 }
11085
11086 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11087 int
11088 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11089 {
11090     int retVal;
11091
11092     if (gameNumber > nCmailGames) {
11093         DisplayError(_("No more games in this message"), 0);
11094         return FALSE;
11095     }
11096     if (f == lastLoadGameFP) {
11097         int offset = gameNumber - lastLoadGameNumber;
11098         if (offset == 0) {
11099             cmailMsg[0] = NULLCHAR;
11100             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11101                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11102                 nCmailMovesRegistered--;
11103             }
11104             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11105             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11106                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11107             }
11108         } else {
11109             if (! RegisterMove()) return FALSE;
11110         }
11111     }
11112
11113     retVal = LoadGame(f, gameNumber, title, useList);
11114
11115     /* Make move registered during previous look at this game, if any */
11116     MakeRegisteredMove();
11117
11118     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11119         commentList[currentMove]
11120           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11121         DisplayComment(currentMove - 1, commentList[currentMove]);
11122     }
11123
11124     return retVal;
11125 }
11126
11127 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11128 int
11129 ReloadGame (int offset)
11130 {
11131     int gameNumber = lastLoadGameNumber + offset;
11132     if (lastLoadGameFP == NULL) {
11133         DisplayError(_("No game has been loaded yet"), 0);
11134         return FALSE;
11135     }
11136     if (gameNumber <= 0) {
11137         DisplayError(_("Can't back up any further"), 0);
11138         return FALSE;
11139     }
11140     if (cmailMsgLoaded) {
11141         return CmailLoadGame(lastLoadGameFP, gameNumber,
11142                              lastLoadGameTitle, lastLoadGameUseList);
11143     } else {
11144         return LoadGame(lastLoadGameFP, gameNumber,
11145                         lastLoadGameTitle, lastLoadGameUseList);
11146     }
11147 }
11148
11149 int keys[EmptySquare+1];
11150
11151 int
11152 PositionMatches (Board b1, Board b2)
11153 {
11154     int r, f, sum=0;
11155     switch(appData.searchMode) {
11156         case 1: return CompareWithRights(b1, b2);
11157         case 2:
11158             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11159                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11160             }
11161             return TRUE;
11162         case 3:
11163             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11164               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11165                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11166             }
11167             return sum==0;
11168         case 4:
11169             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11170                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11171             }
11172             return sum==0;
11173     }
11174     return TRUE;
11175 }
11176
11177 #define Q_PROMO  4
11178 #define Q_EP     3
11179 #define Q_BCASTL 2
11180 #define Q_WCASTL 1
11181
11182 int pieceList[256], quickBoard[256];
11183 ChessSquare pieceType[256] = { EmptySquare };
11184 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11185 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11186 int soughtTotal, turn;
11187 Boolean epOK, flipSearch;
11188
11189 typedef struct {
11190     unsigned char piece, to;
11191 } Move;
11192
11193 #define DSIZE (250000)
11194
11195 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11196 Move *moveDatabase = initialSpace;
11197 unsigned int movePtr, dataSize = DSIZE;
11198
11199 int
11200 MakePieceList (Board board, int *counts)
11201 {
11202     int r, f, n=Q_PROMO, total=0;
11203     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11204     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11205         int sq = f + (r<<4);
11206         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11207             quickBoard[sq] = ++n;
11208             pieceList[n] = sq;
11209             pieceType[n] = board[r][f];
11210             counts[board[r][f]]++;
11211             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11212             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11213             total++;
11214         }
11215     }
11216     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11217     return total;
11218 }
11219
11220 void
11221 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11222 {
11223     int sq = fromX + (fromY<<4);
11224     int piece = quickBoard[sq];
11225     quickBoard[sq] = 0;
11226     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11227     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11228         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11229         moveDatabase[movePtr++].piece = Q_WCASTL;
11230         quickBoard[sq] = piece;
11231         piece = quickBoard[from]; quickBoard[from] = 0;
11232         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11233     } else
11234     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11235         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11236         moveDatabase[movePtr++].piece = Q_BCASTL;
11237         quickBoard[sq] = piece;
11238         piece = quickBoard[from]; quickBoard[from] = 0;
11239         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11240     } else
11241     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11242         quickBoard[(fromY<<4)+toX] = 0;
11243         moveDatabase[movePtr].piece = Q_EP;
11244         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11245         moveDatabase[movePtr].to = sq;
11246     } else
11247     if(promoPiece != pieceType[piece]) {
11248         moveDatabase[movePtr++].piece = Q_PROMO;
11249         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11250     }
11251     moveDatabase[movePtr].piece = piece;
11252     quickBoard[sq] = piece;
11253     movePtr++;
11254 }
11255
11256 int
11257 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(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
11282 QuickCompare (Board board, int *minCounts, int *maxCounts)
11283 {   // compare according to search mode
11284     int r, f;
11285     switch(appData.searchMode)
11286     {
11287       case 1: // exact position match
11288         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11289         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11290             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11291         }
11292         break;
11293       case 2: // can have extra material on empty squares
11294         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11295             if(board[r][f] == EmptySquare) continue;
11296             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11297         }
11298         break;
11299       case 3: // material with exact Pawn structure
11300         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11301             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11302             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11303         } // fall through to material comparison
11304       case 4: // exact material
11305         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11306         break;
11307       case 6: // material range with given imbalance
11308         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11309         // fall through to range comparison
11310       case 5: // material range
11311         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11312     }
11313     return TRUE;
11314 }
11315
11316 int
11317 QuickScan (Board board, Move *move)
11318 {   // reconstruct game,and compare all positions in it
11319     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11320     do {
11321         int piece = move->piece;
11322         int to = move->to, from = pieceList[piece];
11323         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11324           if(!piece) return -1;
11325           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11326             piece = (++move)->piece;
11327             from = pieceList[piece];
11328             counts[pieceType[piece]]--;
11329             pieceType[piece] = (ChessSquare) move->to;
11330             counts[move->to]++;
11331           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11332             counts[pieceType[quickBoard[to]]]--;
11333             quickBoard[to] = 0; total--;
11334             move++;
11335             continue;
11336           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11337             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11338             from  = pieceList[piece]; // so this must be King
11339             quickBoard[from] = 0;
11340             quickBoard[to] = piece;
11341             pieceList[piece] = to;
11342             move++;
11343             continue;
11344           }
11345         }
11346         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11347         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11348         quickBoard[from] = 0;
11349         quickBoard[to] = piece;
11350         pieceList[piece] = to;
11351         cnt++; turn ^= 3;
11352         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11353            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11354            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11355                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11356           ) {
11357             static int lastCounts[EmptySquare+1];
11358             int i;
11359             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11360             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11361         } else stretch = 0;
11362         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11363         move++;
11364     } while(1);
11365 }
11366
11367 void
11368 InitSearch ()
11369 {
11370     int r, f;
11371     flipSearch = FALSE;
11372     CopyBoard(soughtBoard, boards[currentMove]);
11373     soughtTotal = MakePieceList(soughtBoard, maxSought);
11374     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11375     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11376     CopyBoard(reverseBoard, boards[currentMove]);
11377     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11378         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11379         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11380         reverseBoard[r][f] = piece;
11381     }
11382     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11383     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11384     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11385                  || (boards[currentMove][CASTLING][2] == NoRights || 
11386                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11387                  && (boards[currentMove][CASTLING][5] == NoRights || 
11388                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11389       ) {
11390         flipSearch = TRUE;
11391         CopyBoard(flipBoard, soughtBoard);
11392         CopyBoard(rotateBoard, reverseBoard);
11393         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11394             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11395             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11396         }
11397     }
11398     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11399     if(appData.searchMode >= 5) {
11400         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11401         MakePieceList(soughtBoard, minSought);
11402         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11403     }
11404     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11405         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11406 }
11407
11408 GameInfo dummyInfo;
11409
11410 int
11411 GameContainsPosition (FILE *f, ListGame *lg)
11412 {
11413     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11414     int fromX, fromY, toX, toY;
11415     char promoChar;
11416     static int initDone=FALSE;
11417
11418     // weed out games based on numerical tag comparison
11419     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11420     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11421     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11422     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11423     if(!initDone) {
11424         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11425         initDone = TRUE;
11426     }
11427     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11428     else CopyBoard(boards[scratch], initialPosition); // default start position
11429     if(lg->moves) {
11430         turn = btm + 1;
11431         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11432         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11433     }
11434     if(btm) plyNr++;
11435     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11436     fseek(f, lg->offset, 0);
11437     yynewfile(f);
11438     while(1) {
11439         yyboardindex = scratch;
11440         quickFlag = plyNr+1;
11441         next = Myylex();
11442         quickFlag = 0;
11443         switch(next) {
11444             case PGNTag:
11445                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11446             default:
11447                 continue;
11448
11449             case XBoardGame:
11450             case GNUChessGame:
11451                 if(plyNr) return -1; // after we have seen moves, this is for new game
11452               continue;
11453
11454             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11455             case ImpossibleMove:
11456             case WhiteWins: // game ends here with these four
11457             case BlackWins:
11458             case GameIsDrawn:
11459             case GameUnfinished:
11460                 return -1;
11461
11462             case IllegalMove:
11463                 if(appData.testLegality) return -1;
11464             case WhiteCapturesEnPassant:
11465             case BlackCapturesEnPassant:
11466             case WhitePromotion:
11467             case BlackPromotion:
11468             case WhiteNonPromotion:
11469             case BlackNonPromotion:
11470             case NormalMove:
11471             case WhiteKingSideCastle:
11472             case WhiteQueenSideCastle:
11473             case BlackKingSideCastle:
11474             case BlackQueenSideCastle:
11475             case WhiteKingSideCastleWild:
11476             case WhiteQueenSideCastleWild:
11477             case BlackKingSideCastleWild:
11478             case BlackQueenSideCastleWild:
11479             case WhiteHSideCastleFR:
11480             case WhiteASideCastleFR:
11481             case BlackHSideCastleFR:
11482             case BlackASideCastleFR:
11483                 fromX = currentMoveString[0] - AAA;
11484                 fromY = currentMoveString[1] - ONE;
11485                 toX = currentMoveString[2] - AAA;
11486                 toY = currentMoveString[3] - ONE;
11487                 promoChar = currentMoveString[4];
11488                 break;
11489             case WhiteDrop:
11490             case BlackDrop:
11491                 fromX = next == WhiteDrop ?
11492                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11493                   (int) CharToPiece(ToLower(currentMoveString[0]));
11494                 fromY = DROP_RANK;
11495                 toX = currentMoveString[2] - AAA;
11496                 toY = currentMoveString[3] - ONE;
11497                 promoChar = 0;
11498                 break;
11499         }
11500         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11501         plyNr++;
11502         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11503         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11504         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11505         if(appData.findMirror) {
11506             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11507             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11508         }
11509     }
11510 }
11511
11512 /* Load the nth game from open file f */
11513 int
11514 LoadGame (FILE *f, int gameNumber, char *title, 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 %s %s", lg->gameInfo.white, _("vs."),
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 (int offset)
11966 {
11967     int positionNumber = lastLoadPositionNumber + offset;
11968     if (lastLoadPositionFP == NULL) {
11969         DisplayError(_("No position has been loaded yet"), 0);
11970         return FALSE;
11971     }
11972     if (positionNumber <= 0) {
11973         DisplayError(_("Can't back up any further"), 0);
11974         return FALSE;
11975     }
11976     return LoadPosition(lastLoadPositionFP, positionNumber,
11977                         lastLoadPositionTitle);
11978 }
11979
11980 /* Load the nth position from the given file */
11981 int
11982 LoadPositionFromFile (char *filename, int n, char *title)
11983 {
11984     FILE *f;
11985     char buf[MSG_SIZ];
11986
11987     if (strcmp(filename, "-") == 0) {
11988         return LoadPosition(stdin, n, "stdin");
11989     } else {
11990         f = fopen(filename, "rb");
11991         if (f == NULL) {
11992             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11993             DisplayError(buf, errno);
11994             return FALSE;
11995         } else {
11996             return LoadPosition(f, n, title);
11997         }
11998     }
11999 }
12000
12001 /* Load the nth position from the given open file, and close it */
12002 int
12003 LoadPosition (FILE *f, int positionNumber, char *title)
12004 {
12005     char *p, line[MSG_SIZ];
12006     Board initial_position;
12007     int i, j, fenMode, pn;
12008
12009     if (gameMode == Training )
12010         SetTrainingModeOff();
12011
12012     if (gameMode != BeginningOfGame) {
12013         Reset(FALSE, TRUE);
12014     }
12015     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12016         fclose(lastLoadPositionFP);
12017     }
12018     if (positionNumber == 0) positionNumber = 1;
12019     lastLoadPositionFP = f;
12020     lastLoadPositionNumber = positionNumber;
12021     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12022     if (first.pr == NoProc && !appData.noChessProgram) {
12023       StartChessProgram(&first);
12024       InitChessProgram(&first, FALSE);
12025     }
12026     pn = positionNumber;
12027     if (positionNumber < 0) {
12028         /* Negative position number means to seek to that byte offset */
12029         if (fseek(f, -positionNumber, 0) == -1) {
12030             DisplayError(_("Can't seek on position file"), 0);
12031             return FALSE;
12032         };
12033         pn = 1;
12034     } else {
12035         if (fseek(f, 0, 0) == -1) {
12036             if (f == lastLoadPositionFP ?
12037                 positionNumber == lastLoadPositionNumber + 1 :
12038                 positionNumber == 1) {
12039                 pn = 1;
12040             } else {
12041                 DisplayError(_("Can't seek on position file"), 0);
12042                 return FALSE;
12043             }
12044         }
12045     }
12046     /* See if this file is FEN or old-style xboard */
12047     if (fgets(line, MSG_SIZ, f) == NULL) {
12048         DisplayError(_("Position not found in file"), 0);
12049         return FALSE;
12050     }
12051     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12052     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12053
12054     if (pn >= 2) {
12055         if (fenMode || line[0] == '#') pn--;
12056         while (pn > 0) {
12057             /* skip positions before number pn */
12058             if (fgets(line, MSG_SIZ, f) == NULL) {
12059                 Reset(TRUE, TRUE);
12060                 DisplayError(_("Position not found in file"), 0);
12061                 return FALSE;
12062             }
12063             if (fenMode || line[0] == '#') pn--;
12064         }
12065     }
12066
12067     if (fenMode) {
12068         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12069             DisplayError(_("Bad FEN position in file"), 0);
12070             return FALSE;
12071         }
12072     } else {
12073         (void) fgets(line, MSG_SIZ, f);
12074         (void) fgets(line, MSG_SIZ, f);
12075
12076         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12077             (void) fgets(line, MSG_SIZ, f);
12078             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12079                 if (*p == ' ')
12080                   continue;
12081                 initial_position[i][j++] = CharToPiece(*p);
12082             }
12083         }
12084
12085         blackPlaysFirst = FALSE;
12086         if (!feof(f)) {
12087             (void) fgets(line, MSG_SIZ, f);
12088             if (strncmp(line, "black", strlen("black"))==0)
12089               blackPlaysFirst = TRUE;
12090         }
12091     }
12092     startedFromSetupPosition = TRUE;
12093
12094     CopyBoard(boards[0], initial_position);
12095     if (blackPlaysFirst) {
12096         currentMove = forwardMostMove = backwardMostMove = 1;
12097         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12098         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12099         CopyBoard(boards[1], initial_position);
12100         DisplayMessage("", _("Black to play"));
12101     } else {
12102         currentMove = forwardMostMove = backwardMostMove = 0;
12103         DisplayMessage("", _("White to play"));
12104     }
12105     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12106     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12107         SendToProgram("force\n", &first);
12108         SendBoard(&first, forwardMostMove);
12109     }
12110     if (appData.debugMode) {
12111 int i, j;
12112   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12113   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12114         fprintf(debugFP, "Load Position\n");
12115     }
12116
12117     if (positionNumber > 1) {
12118       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12119         DisplayTitle(line);
12120     } else {
12121         DisplayTitle(title);
12122     }
12123     gameMode = EditGame;
12124     ModeHighlight();
12125     ResetClocks();
12126     timeRemaining[0][1] = whiteTimeRemaining;
12127     timeRemaining[1][1] = blackTimeRemaining;
12128     DrawPosition(FALSE, boards[currentMove]);
12129
12130     return TRUE;
12131 }
12132
12133
12134 void
12135 CopyPlayerNameIntoFileName (char **dest, char *src)
12136 {
12137     while (*src != NULLCHAR && *src != ',') {
12138         if (*src == ' ') {
12139             *(*dest)++ = '_';
12140             src++;
12141         } else {
12142             *(*dest)++ = *src++;
12143         }
12144     }
12145 }
12146
12147 char *
12148 DefaultFileName (char *ext)
12149 {
12150     static char def[MSG_SIZ];
12151     char *p;
12152
12153     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12154         p = def;
12155         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12156         *p++ = '-';
12157         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12158         *p++ = '.';
12159         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12160     } else {
12161         def[0] = NULLCHAR;
12162     }
12163     return def;
12164 }
12165
12166 /* Save the current game to the given file */
12167 int
12168 SaveGameToFile (char *filename, int append)
12169 {
12170     FILE *f;
12171     char buf[MSG_SIZ];
12172     int result, i, t,tot=0;
12173
12174     if (strcmp(filename, "-") == 0) {
12175         return SaveGame(stdout, 0, NULL);
12176     } else {
12177         for(i=0; i<10; i++) { // upto 10 tries
12178              f = fopen(filename, append ? "a" : "w");
12179              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12180              if(f || errno != 13) break;
12181              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12182              tot += t;
12183         }
12184         if (f == NULL) {
12185             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12186             DisplayError(buf, errno);
12187             return FALSE;
12188         } else {
12189             safeStrCpy(buf, lastMsg, MSG_SIZ);
12190             DisplayMessage(_("Waiting for access to save file"), "");
12191             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12192             DisplayMessage(_("Saving game"), "");
12193             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12194             result = SaveGame(f, 0, NULL);
12195             DisplayMessage(buf, "");
12196             return result;
12197         }
12198     }
12199 }
12200
12201 char *
12202 SavePart (char *str)
12203 {
12204     static char buf[MSG_SIZ];
12205     char *p;
12206
12207     p = strchr(str, ' ');
12208     if (p == NULL) return str;
12209     strncpy(buf, str, p - str);
12210     buf[p - str] = NULLCHAR;
12211     return buf;
12212 }
12213
12214 #define PGN_MAX_LINE 75
12215
12216 #define PGN_SIDE_WHITE  0
12217 #define PGN_SIDE_BLACK  1
12218
12219 static int
12220 FindFirstMoveOutOfBook (int side)
12221 {
12222     int result = -1;
12223
12224     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12225         int index = backwardMostMove;
12226         int has_book_hit = 0;
12227
12228         if( (index % 2) != side ) {
12229             index++;
12230         }
12231
12232         while( index < forwardMostMove ) {
12233             /* Check to see if engine is in book */
12234             int depth = pvInfoList[index].depth;
12235             int score = pvInfoList[index].score;
12236             int in_book = 0;
12237
12238             if( depth <= 2 ) {
12239                 in_book = 1;
12240             }
12241             else if( score == 0 && depth == 63 ) {
12242                 in_book = 1; /* Zappa */
12243             }
12244             else if( score == 2 && depth == 99 ) {
12245                 in_book = 1; /* Abrok */
12246             }
12247
12248             has_book_hit += in_book;
12249
12250             if( ! in_book ) {
12251                 result = index;
12252
12253                 break;
12254             }
12255
12256             index += 2;
12257         }
12258     }
12259
12260     return result;
12261 }
12262
12263 void
12264 GetOutOfBookInfo (char * buf)
12265 {
12266     int oob[2];
12267     int i;
12268     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12269
12270     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12271     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12272
12273     *buf = '\0';
12274
12275     if( oob[0] >= 0 || oob[1] >= 0 ) {
12276         for( i=0; i<2; i++ ) {
12277             int idx = oob[i];
12278
12279             if( idx >= 0 ) {
12280                 if( i > 0 && oob[0] >= 0 ) {
12281                     strcat( buf, "   " );
12282                 }
12283
12284                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12285                 sprintf( buf+strlen(buf), "%s%.2f",
12286                     pvInfoList[idx].score >= 0 ? "+" : "",
12287                     pvInfoList[idx].score / 100.0 );
12288             }
12289         }
12290     }
12291 }
12292
12293 /* Save game in PGN style and close the file */
12294 int
12295 SaveGamePGN (FILE *f)
12296 {
12297     int i, offset, linelen, newblock;
12298     time_t tm;
12299 //    char *movetext;
12300     char numtext[32];
12301     int movelen, numlen, blank;
12302     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12303
12304     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12305
12306     tm = time((time_t *) NULL);
12307
12308     PrintPGNTags(f, &gameInfo);
12309
12310     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12311
12312     if (backwardMostMove > 0 || startedFromSetupPosition) {
12313         char *fen = PositionToFEN(backwardMostMove, NULL);
12314         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12315         fprintf(f, "\n{--------------\n");
12316         PrintPosition(f, backwardMostMove);
12317         fprintf(f, "--------------}\n");
12318         free(fen);
12319     }
12320     else {
12321         /* [AS] Out of book annotation */
12322         if( appData.saveOutOfBookInfo ) {
12323             char buf[64];
12324
12325             GetOutOfBookInfo( buf );
12326
12327             if( buf[0] != '\0' ) {
12328                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12329             }
12330         }
12331
12332         fprintf(f, "\n");
12333     }
12334
12335     i = backwardMostMove;
12336     linelen = 0;
12337     newblock = TRUE;
12338
12339     while (i < forwardMostMove) {
12340         /* Print comments preceding this move */
12341         if (commentList[i] != NULL) {
12342             if (linelen > 0) fprintf(f, "\n");
12343             fprintf(f, "%s", commentList[i]);
12344             linelen = 0;
12345             newblock = TRUE;
12346         }
12347
12348         /* Format move number */
12349         if ((i % 2) == 0)
12350           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12351         else
12352           if (newblock)
12353             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12354           else
12355             numtext[0] = NULLCHAR;
12356
12357         numlen = strlen(numtext);
12358         newblock = FALSE;
12359
12360         /* Print move number */
12361         blank = linelen > 0 && numlen > 0;
12362         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12363             fprintf(f, "\n");
12364             linelen = 0;
12365             blank = 0;
12366         }
12367         if (blank) {
12368             fprintf(f, " ");
12369             linelen++;
12370         }
12371         fprintf(f, "%s", numtext);
12372         linelen += numlen;
12373
12374         /* Get move */
12375         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12376         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12377
12378         /* Print move */
12379         blank = linelen > 0 && movelen > 0;
12380         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12381             fprintf(f, "\n");
12382             linelen = 0;
12383             blank = 0;
12384         }
12385         if (blank) {
12386             fprintf(f, " ");
12387             linelen++;
12388         }
12389         fprintf(f, "%s", move_buffer);
12390         linelen += movelen;
12391
12392         /* [AS] Add PV info if present */
12393         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12394             /* [HGM] add time */
12395             char buf[MSG_SIZ]; int seconds;
12396
12397             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12398
12399             if( seconds <= 0)
12400               buf[0] = 0;
12401             else
12402               if( seconds < 30 )
12403                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12404               else
12405                 {
12406                   seconds = (seconds + 4)/10; // round to full seconds
12407                   if( seconds < 60 )
12408                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12409                   else
12410                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12411                 }
12412
12413             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12414                       pvInfoList[i].score >= 0 ? "+" : "",
12415                       pvInfoList[i].score / 100.0,
12416                       pvInfoList[i].depth,
12417                       buf );
12418
12419             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12420
12421             /* Print score/depth */
12422             blank = linelen > 0 && movelen > 0;
12423             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12424                 fprintf(f, "\n");
12425                 linelen = 0;
12426                 blank = 0;
12427             }
12428             if (blank) {
12429                 fprintf(f, " ");
12430                 linelen++;
12431             }
12432             fprintf(f, "%s", move_buffer);
12433             linelen += movelen;
12434         }
12435
12436         i++;
12437     }
12438
12439     /* Start a new line */
12440     if (linelen > 0) fprintf(f, "\n");
12441
12442     /* Print comments after last move */
12443     if (commentList[i] != NULL) {
12444         fprintf(f, "%s\n", commentList[i]);
12445     }
12446
12447     /* Print result */
12448     if (gameInfo.resultDetails != NULL &&
12449         gameInfo.resultDetails[0] != NULLCHAR) {
12450         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12451                 PGNResult(gameInfo.result));
12452     } else {
12453         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12454     }
12455
12456     fclose(f);
12457     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12458     return TRUE;
12459 }
12460
12461 /* Save game in old style and close the file */
12462 int
12463 SaveGameOldStyle (FILE *f)
12464 {
12465     int i, offset;
12466     time_t tm;
12467
12468     tm = time((time_t *) NULL);
12469
12470     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12471     PrintOpponents(f);
12472
12473     if (backwardMostMove > 0 || startedFromSetupPosition) {
12474         fprintf(f, "\n[--------------\n");
12475         PrintPosition(f, backwardMostMove);
12476         fprintf(f, "--------------]\n");
12477     } else {
12478         fprintf(f, "\n");
12479     }
12480
12481     i = backwardMostMove;
12482     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12483
12484     while (i < forwardMostMove) {
12485         if (commentList[i] != NULL) {
12486             fprintf(f, "[%s]\n", commentList[i]);
12487         }
12488
12489         if ((i % 2) == 1) {
12490             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12491             i++;
12492         } else {
12493             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12494             i++;
12495             if (commentList[i] != NULL) {
12496                 fprintf(f, "\n");
12497                 continue;
12498             }
12499             if (i >= forwardMostMove) {
12500                 fprintf(f, "\n");
12501                 break;
12502             }
12503             fprintf(f, "%s\n", parseList[i]);
12504             i++;
12505         }
12506     }
12507
12508     if (commentList[i] != NULL) {
12509         fprintf(f, "[%s]\n", commentList[i]);
12510     }
12511
12512     /* This isn't really the old style, but it's close enough */
12513     if (gameInfo.resultDetails != NULL &&
12514         gameInfo.resultDetails[0] != NULLCHAR) {
12515         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12516                 gameInfo.resultDetails);
12517     } else {
12518         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12519     }
12520
12521     fclose(f);
12522     return TRUE;
12523 }
12524
12525 /* Save the current game to open file f and close the file */
12526 int
12527 SaveGame (FILE *f, int dummy, char *dummy2)
12528 {
12529     if (gameMode == EditPosition) EditPositionDone(TRUE);
12530     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12531     if (appData.oldSaveStyle)
12532       return SaveGameOldStyle(f);
12533     else
12534       return SaveGamePGN(f);
12535 }
12536
12537 /* Save the current position to the given file */
12538 int
12539 SavePositionToFile (char *filename)
12540 {
12541     FILE *f;
12542     char buf[MSG_SIZ];
12543
12544     if (strcmp(filename, "-") == 0) {
12545         return SavePosition(stdout, 0, NULL);
12546     } else {
12547         f = fopen(filename, "a");
12548         if (f == NULL) {
12549             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12550             DisplayError(buf, errno);
12551             return FALSE;
12552         } else {
12553             safeStrCpy(buf, lastMsg, MSG_SIZ);
12554             DisplayMessage(_("Waiting for access to save file"), "");
12555             flock(fileno(f), LOCK_EX); // [HGM] lock
12556             DisplayMessage(_("Saving position"), "");
12557             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12558             SavePosition(f, 0, NULL);
12559             DisplayMessage(buf, "");
12560             return TRUE;
12561         }
12562     }
12563 }
12564
12565 /* Save the current position to the given open file and close the file */
12566 int
12567 SavePosition (FILE *f, int dummy, char *dummy2)
12568 {
12569     time_t tm;
12570     char *fen;
12571
12572     if (gameMode == EditPosition) EditPositionDone(TRUE);
12573     if (appData.oldSaveStyle) {
12574         tm = time((time_t *) NULL);
12575
12576         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12577         PrintOpponents(f);
12578         fprintf(f, "[--------------\n");
12579         PrintPosition(f, currentMove);
12580         fprintf(f, "--------------]\n");
12581     } else {
12582         fen = PositionToFEN(currentMove, NULL);
12583         fprintf(f, "%s\n", fen);
12584         free(fen);
12585     }
12586     fclose(f);
12587     return TRUE;
12588 }
12589
12590 void
12591 ReloadCmailMsgEvent (int unregister)
12592 {
12593 #if !WIN32
12594     static char *inFilename = NULL;
12595     static char *outFilename;
12596     int i;
12597     struct stat inbuf, outbuf;
12598     int status;
12599
12600     /* Any registered moves are unregistered if unregister is set, */
12601     /* i.e. invoked by the signal handler */
12602     if (unregister) {
12603         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12604             cmailMoveRegistered[i] = FALSE;
12605             if (cmailCommentList[i] != NULL) {
12606                 free(cmailCommentList[i]);
12607                 cmailCommentList[i] = NULL;
12608             }
12609         }
12610         nCmailMovesRegistered = 0;
12611     }
12612
12613     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12614         cmailResult[i] = CMAIL_NOT_RESULT;
12615     }
12616     nCmailResults = 0;
12617
12618     if (inFilename == NULL) {
12619         /* Because the filenames are static they only get malloced once  */
12620         /* and they never get freed                                      */
12621         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12622         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12623
12624         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12625         sprintf(outFilename, "%s.out", appData.cmailGameName);
12626     }
12627
12628     status = stat(outFilename, &outbuf);
12629     if (status < 0) {
12630         cmailMailedMove = FALSE;
12631     } else {
12632         status = stat(inFilename, &inbuf);
12633         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12634     }
12635
12636     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12637        counts the games, notes how each one terminated, etc.
12638
12639        It would be nice to remove this kludge and instead gather all
12640        the information while building the game list.  (And to keep it
12641        in the game list nodes instead of having a bunch of fixed-size
12642        parallel arrays.)  Note this will require getting each game's
12643        termination from the PGN tags, as the game list builder does
12644        not process the game moves.  --mann
12645        */
12646     cmailMsgLoaded = TRUE;
12647     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12648
12649     /* Load first game in the file or popup game menu */
12650     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12651
12652 #endif /* !WIN32 */
12653     return;
12654 }
12655
12656 int
12657 RegisterMove ()
12658 {
12659     FILE *f;
12660     char string[MSG_SIZ];
12661
12662     if (   cmailMailedMove
12663         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12664         return TRUE;            /* Allow free viewing  */
12665     }
12666
12667     /* Unregister move to ensure that we don't leave RegisterMove        */
12668     /* with the move registered when the conditions for registering no   */
12669     /* longer hold                                                       */
12670     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12671         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12672         nCmailMovesRegistered --;
12673
12674         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12675           {
12676               free(cmailCommentList[lastLoadGameNumber - 1]);
12677               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12678           }
12679     }
12680
12681     if (cmailOldMove == -1) {
12682         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12683         return FALSE;
12684     }
12685
12686     if (currentMove > cmailOldMove + 1) {
12687         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12688         return FALSE;
12689     }
12690
12691     if (currentMove < cmailOldMove) {
12692         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12693         return FALSE;
12694     }
12695
12696     if (forwardMostMove > currentMove) {
12697         /* Silently truncate extra moves */
12698         TruncateGame();
12699     }
12700
12701     if (   (currentMove == cmailOldMove + 1)
12702         || (   (currentMove == cmailOldMove)
12703             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12704                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12705         if (gameInfo.result != GameUnfinished) {
12706             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12707         }
12708
12709         if (commentList[currentMove] != NULL) {
12710             cmailCommentList[lastLoadGameNumber - 1]
12711               = StrSave(commentList[currentMove]);
12712         }
12713         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12714
12715         if (appData.debugMode)
12716           fprintf(debugFP, "Saving %s for game %d\n",
12717                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12718
12719         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12720
12721         f = fopen(string, "w");
12722         if (appData.oldSaveStyle) {
12723             SaveGameOldStyle(f); /* also closes the file */
12724
12725             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12726             f = fopen(string, "w");
12727             SavePosition(f, 0, NULL); /* also closes the file */
12728         } else {
12729             fprintf(f, "{--------------\n");
12730             PrintPosition(f, currentMove);
12731             fprintf(f, "--------------}\n\n");
12732
12733             SaveGame(f, 0, NULL); /* also closes the file*/
12734         }
12735
12736         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12737         nCmailMovesRegistered ++;
12738     } else if (nCmailGames == 1) {
12739         DisplayError(_("You have not made a move yet"), 0);
12740         return FALSE;
12741     }
12742
12743     return TRUE;
12744 }
12745
12746 void
12747 MailMoveEvent ()
12748 {
12749 #if !WIN32
12750     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12751     FILE *commandOutput;
12752     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12753     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12754     int nBuffers;
12755     int i;
12756     int archived;
12757     char *arcDir;
12758
12759     if (! cmailMsgLoaded) {
12760         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12761         return;
12762     }
12763
12764     if (nCmailGames == nCmailResults) {
12765         DisplayError(_("No unfinished games"), 0);
12766         return;
12767     }
12768
12769 #if CMAIL_PROHIBIT_REMAIL
12770     if (cmailMailedMove) {
12771       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);
12772         DisplayError(msg, 0);
12773         return;
12774     }
12775 #endif
12776
12777     if (! (cmailMailedMove || RegisterMove())) return;
12778
12779     if (   cmailMailedMove
12780         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12781       snprintf(string, MSG_SIZ, partCommandString,
12782                appData.debugMode ? " -v" : "", appData.cmailGameName);
12783         commandOutput = popen(string, "r");
12784
12785         if (commandOutput == NULL) {
12786             DisplayError(_("Failed to invoke cmail"), 0);
12787         } else {
12788             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12789                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12790             }
12791             if (nBuffers > 1) {
12792                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12793                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12794                 nBytes = MSG_SIZ - 1;
12795             } else {
12796                 (void) memcpy(msg, buffer, nBytes);
12797             }
12798             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12799
12800             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12801                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12802
12803                 archived = TRUE;
12804                 for (i = 0; i < nCmailGames; i ++) {
12805                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12806                         archived = FALSE;
12807                     }
12808                 }
12809                 if (   archived
12810                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12811                         != NULL)) {
12812                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12813                            arcDir,
12814                            appData.cmailGameName,
12815                            gameInfo.date);
12816                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12817                     cmailMsgLoaded = FALSE;
12818                 }
12819             }
12820
12821             DisplayInformation(msg);
12822             pclose(commandOutput);
12823         }
12824     } else {
12825         if ((*cmailMsg) != '\0') {
12826             DisplayInformation(cmailMsg);
12827         }
12828     }
12829
12830     return;
12831 #endif /* !WIN32 */
12832 }
12833
12834 char *
12835 CmailMsg ()
12836 {
12837 #if WIN32
12838     return NULL;
12839 #else
12840     int  prependComma = 0;
12841     char number[5];
12842     char string[MSG_SIZ];       /* Space for game-list */
12843     int  i;
12844
12845     if (!cmailMsgLoaded) return "";
12846
12847     if (cmailMailedMove) {
12848       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12849     } else {
12850         /* Create a list of games left */
12851       snprintf(string, MSG_SIZ, "[");
12852         for (i = 0; i < nCmailGames; i ++) {
12853             if (! (   cmailMoveRegistered[i]
12854                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12855                 if (prependComma) {
12856                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12857                 } else {
12858                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12859                     prependComma = 1;
12860                 }
12861
12862                 strcat(string, number);
12863             }
12864         }
12865         strcat(string, "]");
12866
12867         if (nCmailMovesRegistered + nCmailResults == 0) {
12868             switch (nCmailGames) {
12869               case 1:
12870                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12871                 break;
12872
12873               case 2:
12874                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12875                 break;
12876
12877               default:
12878                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12879                          nCmailGames);
12880                 break;
12881             }
12882         } else {
12883             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12884               case 1:
12885                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12886                          string);
12887                 break;
12888
12889               case 0:
12890                 if (nCmailResults == nCmailGames) {
12891                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12892                 } else {
12893                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12894                 }
12895                 break;
12896
12897               default:
12898                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12899                          string);
12900             }
12901         }
12902     }
12903     return cmailMsg;
12904 #endif /* WIN32 */
12905 }
12906
12907 void
12908 ResetGameEvent ()
12909 {
12910     if (gameMode == Training)
12911       SetTrainingModeOff();
12912
12913     Reset(TRUE, TRUE);
12914     cmailMsgLoaded = FALSE;
12915     if (appData.icsActive) {
12916       SendToICS(ics_prefix);
12917       SendToICS("refresh\n");
12918     }
12919 }
12920
12921 void
12922 ExitEvent (int status)
12923 {
12924     exiting++;
12925     if (exiting > 2) {
12926       /* Give up on clean exit */
12927       exit(status);
12928     }
12929     if (exiting > 1) {
12930       /* Keep trying for clean exit */
12931       return;
12932     }
12933
12934     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12935
12936     if (telnetISR != NULL) {
12937       RemoveInputSource(telnetISR);
12938     }
12939     if (icsPR != NoProc) {
12940       DestroyChildProcess(icsPR, TRUE);
12941     }
12942
12943     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12944     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12945
12946     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12947     /* make sure this other one finishes before killing it!                  */
12948     if(endingGame) { int count = 0;
12949         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12950         while(endingGame && count++ < 10) DoSleep(1);
12951         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12952     }
12953
12954     /* Kill off chess programs */
12955     if (first.pr != NoProc) {
12956         ExitAnalyzeMode();
12957
12958         DoSleep( appData.delayBeforeQuit );
12959         SendToProgram("quit\n", &first);
12960         DoSleep( appData.delayAfterQuit );
12961         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12962     }
12963     if (second.pr != NoProc) {
12964         DoSleep( appData.delayBeforeQuit );
12965         SendToProgram("quit\n", &second);
12966         DoSleep( appData.delayAfterQuit );
12967         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12968     }
12969     if (first.isr != NULL) {
12970         RemoveInputSource(first.isr);
12971     }
12972     if (second.isr != NULL) {
12973         RemoveInputSource(second.isr);
12974     }
12975
12976     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12977     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12978
12979     ShutDownFrontEnd();
12980     exit(status);
12981 }
12982
12983 void
12984 PauseEvent ()
12985 {
12986     if (appData.debugMode)
12987         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12988     if (pausing) {
12989         pausing = FALSE;
12990         ModeHighlight();
12991         if (gameMode == MachinePlaysWhite ||
12992             gameMode == MachinePlaysBlack) {
12993             StartClocks();
12994         } else {
12995             DisplayBothClocks();
12996         }
12997         if (gameMode == PlayFromGameFile) {
12998             if (appData.timeDelay >= 0)
12999                 AutoPlayGameLoop();
13000         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13001             Reset(FALSE, TRUE);
13002             SendToICS(ics_prefix);
13003             SendToICS("refresh\n");
13004         } else if (currentMove < forwardMostMove) {
13005             ForwardInner(forwardMostMove);
13006         }
13007         pauseExamInvalid = FALSE;
13008     } else {
13009         switch (gameMode) {
13010           default:
13011             return;
13012           case IcsExamining:
13013             pauseExamForwardMostMove = forwardMostMove;
13014             pauseExamInvalid = FALSE;
13015             /* fall through */
13016           case IcsObserving:
13017           case IcsPlayingWhite:
13018           case IcsPlayingBlack:
13019             pausing = TRUE;
13020             ModeHighlight();
13021             return;
13022           case PlayFromGameFile:
13023             (void) StopLoadGameTimer();
13024             pausing = TRUE;
13025             ModeHighlight();
13026             break;
13027           case BeginningOfGame:
13028             if (appData.icsActive) return;
13029             /* else fall through */
13030           case MachinePlaysWhite:
13031           case MachinePlaysBlack:
13032           case TwoMachinesPlay:
13033             if (forwardMostMove == 0)
13034               return;           /* don't pause if no one has moved */
13035             if ((gameMode == MachinePlaysWhite &&
13036                  !WhiteOnMove(forwardMostMove)) ||
13037                 (gameMode == MachinePlaysBlack &&
13038                  WhiteOnMove(forwardMostMove))) {
13039                 StopClocks();
13040             }
13041             pausing = TRUE;
13042             ModeHighlight();
13043             break;
13044         }
13045     }
13046 }
13047
13048 void
13049 EditCommentEvent ()
13050 {
13051     char title[MSG_SIZ];
13052
13053     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13054       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13055     } else {
13056       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13057                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13058                parseList[currentMove - 1]);
13059     }
13060
13061     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13062 }
13063
13064
13065 void
13066 EditTagsEvent ()
13067 {
13068     char *tags = PGNTags(&gameInfo);
13069     bookUp = FALSE;
13070     EditTagsPopUp(tags, NULL);
13071     free(tags);
13072 }
13073
13074 void
13075 AnalyzeModeEvent ()
13076 {
13077     if (appData.noChessProgram || gameMode == AnalyzeMode)
13078       return;
13079
13080     if (gameMode != AnalyzeFile) {
13081         if (!appData.icsEngineAnalyze) {
13082                EditGameEvent();
13083                if (gameMode != EditGame) return;
13084         }
13085         ResurrectChessProgram();
13086         SendToProgram("analyze\n", &first);
13087         first.analyzing = TRUE;
13088         /*first.maybeThinking = TRUE;*/
13089         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13090         EngineOutputPopUp();
13091     }
13092     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13093     pausing = FALSE;
13094     ModeHighlight();
13095     SetGameInfo();
13096
13097     StartAnalysisClock();
13098     GetTimeMark(&lastNodeCountTime);
13099     lastNodeCount = 0;
13100 }
13101
13102 void
13103 AnalyzeFileEvent ()
13104 {
13105     if (appData.noChessProgram || gameMode == AnalyzeFile)
13106       return;
13107
13108     if (gameMode != AnalyzeMode) {
13109         EditGameEvent();
13110         if (gameMode != EditGame) return;
13111         ResurrectChessProgram();
13112         SendToProgram("analyze\n", &first);
13113         first.analyzing = TRUE;
13114         /*first.maybeThinking = TRUE;*/
13115         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13116         EngineOutputPopUp();
13117     }
13118     gameMode = AnalyzeFile;
13119     pausing = FALSE;
13120     ModeHighlight();
13121     SetGameInfo();
13122
13123     StartAnalysisClock();
13124     GetTimeMark(&lastNodeCountTime);
13125     lastNodeCount = 0;
13126     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13127 }
13128
13129 void
13130 MachineWhiteEvent ()
13131 {
13132     char buf[MSG_SIZ];
13133     char *bookHit = NULL;
13134
13135     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13136       return;
13137
13138
13139     if (gameMode == PlayFromGameFile ||
13140         gameMode == TwoMachinesPlay  ||
13141         gameMode == Training         ||
13142         gameMode == AnalyzeMode      ||
13143         gameMode == EndOfGame)
13144         EditGameEvent();
13145
13146     if (gameMode == EditPosition)
13147         EditPositionDone(TRUE);
13148
13149     if (!WhiteOnMove(currentMove)) {
13150         DisplayError(_("It is not White's turn"), 0);
13151         return;
13152     }
13153
13154     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13155       ExitAnalyzeMode();
13156
13157     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13158         gameMode == AnalyzeFile)
13159         TruncateGame();
13160
13161     ResurrectChessProgram();    /* in case it isn't running */
13162     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13163         gameMode = MachinePlaysWhite;
13164         ResetClocks();
13165     } else
13166     gameMode = MachinePlaysWhite;
13167     pausing = FALSE;
13168     ModeHighlight();
13169     SetGameInfo();
13170     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13171     DisplayTitle(buf);
13172     if (first.sendName) {
13173       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13174       SendToProgram(buf, &first);
13175     }
13176     if (first.sendTime) {
13177       if (first.useColors) {
13178         SendToProgram("black\n", &first); /*gnu kludge*/
13179       }
13180       SendTimeRemaining(&first, TRUE);
13181     }
13182     if (first.useColors) {
13183       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13184     }
13185     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13186     SetMachineThinkingEnables();
13187     first.maybeThinking = TRUE;
13188     StartClocks();
13189     firstMove = FALSE;
13190
13191     if (appData.autoFlipView && !flipView) {
13192       flipView = !flipView;
13193       DrawPosition(FALSE, NULL);
13194       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13195     }
13196
13197     if(bookHit) { // [HGM] book: simulate book reply
13198         static char bookMove[MSG_SIZ]; // a bit generous?
13199
13200         programStats.nodes = programStats.depth = programStats.time =
13201         programStats.score = programStats.got_only_move = 0;
13202         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13203
13204         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13205         strcat(bookMove, bookHit);
13206         HandleMachineMove(bookMove, &first);
13207     }
13208 }
13209
13210 void
13211 MachineBlackEvent ()
13212 {
13213   char buf[MSG_SIZ];
13214   char *bookHit = NULL;
13215
13216     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13217         return;
13218
13219
13220     if (gameMode == PlayFromGameFile ||
13221         gameMode == TwoMachinesPlay  ||
13222         gameMode == Training         ||
13223         gameMode == AnalyzeMode      ||
13224         gameMode == EndOfGame)
13225         EditGameEvent();
13226
13227     if (gameMode == EditPosition)
13228         EditPositionDone(TRUE);
13229
13230     if (WhiteOnMove(currentMove)) {
13231         DisplayError(_("It is not Black's turn"), 0);
13232         return;
13233     }
13234
13235     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13236       ExitAnalyzeMode();
13237
13238     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13239         gameMode == AnalyzeFile)
13240         TruncateGame();
13241
13242     ResurrectChessProgram();    /* in case it isn't running */
13243     gameMode = MachinePlaysBlack;
13244     pausing = FALSE;
13245     ModeHighlight();
13246     SetGameInfo();
13247     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13248     DisplayTitle(buf);
13249     if (first.sendName) {
13250       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13251       SendToProgram(buf, &first);
13252     }
13253     if (first.sendTime) {
13254       if (first.useColors) {
13255         SendToProgram("white\n", &first); /*gnu kludge*/
13256       }
13257       SendTimeRemaining(&first, FALSE);
13258     }
13259     if (first.useColors) {
13260       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13261     }
13262     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13263     SetMachineThinkingEnables();
13264     first.maybeThinking = TRUE;
13265     StartClocks();
13266
13267     if (appData.autoFlipView && flipView) {
13268       flipView = !flipView;
13269       DrawPosition(FALSE, NULL);
13270       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13271     }
13272     if(bookHit) { // [HGM] book: simulate book reply
13273         static char bookMove[MSG_SIZ]; // a bit generous?
13274
13275         programStats.nodes = programStats.depth = programStats.time =
13276         programStats.score = programStats.got_only_move = 0;
13277         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13278
13279         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13280         strcat(bookMove, bookHit);
13281         HandleMachineMove(bookMove, &first);
13282     }
13283 }
13284
13285
13286 void
13287 DisplayTwoMachinesTitle ()
13288 {
13289     char buf[MSG_SIZ];
13290     if (appData.matchGames > 0) {
13291         if(appData.tourneyFile[0]) {
13292           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13293                    gameInfo.white, _("vs."), gameInfo.black,
13294                    nextGame+1, appData.matchGames+1,
13295                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13296         } else 
13297         if (first.twoMachinesColor[0] == 'w') {
13298           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13299                    gameInfo.white, _("vs."),  gameInfo.black,
13300                    first.matchWins, second.matchWins,
13301                    matchGame - 1 - (first.matchWins + second.matchWins));
13302         } else {
13303           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13304                    gameInfo.white, _("vs."), gameInfo.black,
13305                    second.matchWins, first.matchWins,
13306                    matchGame - 1 - (first.matchWins + second.matchWins));
13307         }
13308     } else {
13309       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13310     }
13311     DisplayTitle(buf);
13312 }
13313
13314 void
13315 SettingsMenuIfReady ()
13316 {
13317   if (second.lastPing != second.lastPong) {
13318     DisplayMessage("", _("Waiting for second chess program"));
13319     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13320     return;
13321   }
13322   ThawUI();
13323   DisplayMessage("", "");
13324   SettingsPopUp(&second);
13325 }
13326
13327 int
13328 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13329 {
13330     char buf[MSG_SIZ];
13331     if (cps->pr == NoProc) {
13332         StartChessProgram(cps);
13333         if (cps->protocolVersion == 1) {
13334           retry();
13335         } else {
13336           /* kludge: allow timeout for initial "feature" command */
13337           FreezeUI();
13338           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13339           DisplayMessage("", buf);
13340           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13341         }
13342         return 1;
13343     }
13344     return 0;
13345 }
13346
13347 void
13348 TwoMachinesEvent P((void))
13349 {
13350     int i;
13351     char buf[MSG_SIZ];
13352     ChessProgramState *onmove;
13353     char *bookHit = NULL;
13354     static int stalling = 0;
13355     TimeMark now;
13356     long wait;
13357
13358     if (appData.noChessProgram) return;
13359
13360     switch (gameMode) {
13361       case TwoMachinesPlay:
13362         return;
13363       case MachinePlaysWhite:
13364       case MachinePlaysBlack:
13365         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13366             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13367             return;
13368         }
13369         /* fall through */
13370       case BeginningOfGame:
13371       case PlayFromGameFile:
13372       case EndOfGame:
13373         EditGameEvent();
13374         if (gameMode != EditGame) return;
13375         break;
13376       case EditPosition:
13377         EditPositionDone(TRUE);
13378         break;
13379       case AnalyzeMode:
13380       case AnalyzeFile:
13381         ExitAnalyzeMode();
13382         break;
13383       case EditGame:
13384       default:
13385         break;
13386     }
13387
13388     if(matchMode && appData.debugMode) { // [HGM] debug split: game is part of a match; we might have to create a debug file just for this game
13389         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
13390         if(strcmp(buf, currentDebugFile)) { // name has changed
13391             FILE *f = fopen(buf, "w");
13392             if(f) { // if opening the new file failed, just keep using the old one
13393                 ASSIGN(currentDebugFile, buf);
13394                 fclose(debugFP);
13395                 debugFP = f;
13396             }
13397         }
13398     }
13399 //    forwardMostMove = currentMove;
13400     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13401
13402     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13403
13404     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13405     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13406       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13407       return;
13408     }
13409     if(!stalling) {
13410       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13411       SendToProgram("force\n", &second);
13412       stalling = 1;
13413       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13414       return;
13415     }
13416     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13417     if(appData.matchPause>10000 || appData.matchPause<10)
13418                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13419     wait = SubtractTimeMarks(&now, &pauseStart);
13420     if(wait < appData.matchPause) {
13421         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13422         return;
13423     }
13424     // we are now committed to starting the game
13425     stalling = 0;
13426     DisplayMessage("", "");
13427     if (startedFromSetupPosition) {
13428         SendBoard(&second, backwardMostMove);
13429     if (appData.debugMode) {
13430         fprintf(debugFP, "Two Machines\n");
13431     }
13432     }
13433     for (i = backwardMostMove; i < forwardMostMove; i++) {
13434         SendMoveToProgram(i, &second);
13435     }
13436
13437     gameMode = TwoMachinesPlay;
13438     pausing = FALSE;
13439     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13440     SetGameInfo();
13441     DisplayTwoMachinesTitle();
13442     firstMove = TRUE;
13443     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13444         onmove = &first;
13445     } else {
13446         onmove = &second;
13447     }
13448     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13449     SendToProgram(first.computerString, &first);
13450     if (first.sendName) {
13451       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13452       SendToProgram(buf, &first);
13453     }
13454     SendToProgram(second.computerString, &second);
13455     if (second.sendName) {
13456       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13457       SendToProgram(buf, &second);
13458     }
13459
13460     ResetClocks();
13461     if (!first.sendTime || !second.sendTime) {
13462         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13463         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13464     }
13465     if (onmove->sendTime) {
13466       if (onmove->useColors) {
13467         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13468       }
13469       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13470     }
13471     if (onmove->useColors) {
13472       SendToProgram(onmove->twoMachinesColor, onmove);
13473     }
13474     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13475 //    SendToProgram("go\n", onmove);
13476     onmove->maybeThinking = TRUE;
13477     SetMachineThinkingEnables();
13478
13479     StartClocks();
13480
13481     if(bookHit) { // [HGM] book: simulate book reply
13482         static char bookMove[MSG_SIZ]; // a bit generous?
13483
13484         programStats.nodes = programStats.depth = programStats.time =
13485         programStats.score = programStats.got_only_move = 0;
13486         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13487
13488         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13489         strcat(bookMove, bookHit);
13490         savedMessage = bookMove; // args for deferred call
13491         savedState = onmove;
13492         ScheduleDelayedEvent(DeferredBookMove, 1);
13493     }
13494 }
13495
13496 void
13497 TrainingEvent ()
13498 {
13499     if (gameMode == Training) {
13500       SetTrainingModeOff();
13501       gameMode = PlayFromGameFile;
13502       DisplayMessage("", _("Training mode off"));
13503     } else {
13504       gameMode = Training;
13505       animateTraining = appData.animate;
13506
13507       /* make sure we are not already at the end of the game */
13508       if (currentMove < forwardMostMove) {
13509         SetTrainingModeOn();
13510         DisplayMessage("", _("Training mode on"));
13511       } else {
13512         gameMode = PlayFromGameFile;
13513         DisplayError(_("Already at end of game"), 0);
13514       }
13515     }
13516     ModeHighlight();
13517 }
13518
13519 void
13520 IcsClientEvent ()
13521 {
13522     if (!appData.icsActive) return;
13523     switch (gameMode) {
13524       case IcsPlayingWhite:
13525       case IcsPlayingBlack:
13526       case IcsObserving:
13527       case IcsIdle:
13528       case BeginningOfGame:
13529       case IcsExamining:
13530         return;
13531
13532       case EditGame:
13533         break;
13534
13535       case EditPosition:
13536         EditPositionDone(TRUE);
13537         break;
13538
13539       case AnalyzeMode:
13540       case AnalyzeFile:
13541         ExitAnalyzeMode();
13542         break;
13543
13544       default:
13545         EditGameEvent();
13546         break;
13547     }
13548
13549     gameMode = IcsIdle;
13550     ModeHighlight();
13551     return;
13552 }
13553
13554 void
13555 EditGameEvent ()
13556 {
13557     int i;
13558
13559     switch (gameMode) {
13560       case Training:
13561         SetTrainingModeOff();
13562         break;
13563       case MachinePlaysWhite:
13564       case MachinePlaysBlack:
13565       case BeginningOfGame:
13566         SendToProgram("force\n", &first);
13567         SetUserThinkingEnables();
13568         break;
13569       case PlayFromGameFile:
13570         (void) StopLoadGameTimer();
13571         if (gameFileFP != NULL) {
13572             gameFileFP = NULL;
13573         }
13574         break;
13575       case EditPosition:
13576         EditPositionDone(TRUE);
13577         break;
13578       case AnalyzeMode:
13579       case AnalyzeFile:
13580         ExitAnalyzeMode();
13581         SendToProgram("force\n", &first);
13582         break;
13583       case TwoMachinesPlay:
13584         GameEnds(EndOfFile, NULL, GE_PLAYER);
13585         ResurrectChessProgram();
13586         SetUserThinkingEnables();
13587         break;
13588       case EndOfGame:
13589         ResurrectChessProgram();
13590         break;
13591       case IcsPlayingBlack:
13592       case IcsPlayingWhite:
13593         DisplayError(_("Warning: You are still playing a game"), 0);
13594         break;
13595       case IcsObserving:
13596         DisplayError(_("Warning: You are still observing a game"), 0);
13597         break;
13598       case IcsExamining:
13599         DisplayError(_("Warning: You are still examining a game"), 0);
13600         break;
13601       case IcsIdle:
13602         break;
13603       case EditGame:
13604       default:
13605         return;
13606     }
13607
13608     pausing = FALSE;
13609     StopClocks();
13610     first.offeredDraw = second.offeredDraw = 0;
13611
13612     if (gameMode == PlayFromGameFile) {
13613         whiteTimeRemaining = timeRemaining[0][currentMove];
13614         blackTimeRemaining = timeRemaining[1][currentMove];
13615         DisplayTitle("");
13616     }
13617
13618     if (gameMode == MachinePlaysWhite ||
13619         gameMode == MachinePlaysBlack ||
13620         gameMode == TwoMachinesPlay ||
13621         gameMode == EndOfGame) {
13622         i = forwardMostMove;
13623         while (i > currentMove) {
13624             SendToProgram("undo\n", &first);
13625             i--;
13626         }
13627         if(!adjustedClock) {
13628         whiteTimeRemaining = timeRemaining[0][currentMove];
13629         blackTimeRemaining = timeRemaining[1][currentMove];
13630         DisplayBothClocks();
13631         }
13632         if (whiteFlag || blackFlag) {
13633             whiteFlag = blackFlag = 0;
13634         }
13635         DisplayTitle("");
13636     }
13637
13638     gameMode = EditGame;
13639     ModeHighlight();
13640     SetGameInfo();
13641 }
13642
13643
13644 void
13645 EditPositionEvent ()
13646 {
13647     if (gameMode == EditPosition) {
13648         EditGameEvent();
13649         return;
13650     }
13651
13652     EditGameEvent();
13653     if (gameMode != EditGame) return;
13654
13655     gameMode = EditPosition;
13656     ModeHighlight();
13657     SetGameInfo();
13658     if (currentMove > 0)
13659       CopyBoard(boards[0], boards[currentMove]);
13660
13661     blackPlaysFirst = !WhiteOnMove(currentMove);
13662     ResetClocks();
13663     currentMove = forwardMostMove = backwardMostMove = 0;
13664     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13665     DisplayMove(-1);
13666 }
13667
13668 void
13669 ExitAnalyzeMode ()
13670 {
13671     /* [DM] icsEngineAnalyze - possible call from other functions */
13672     if (appData.icsEngineAnalyze) {
13673         appData.icsEngineAnalyze = FALSE;
13674
13675         DisplayMessage("",_("Close ICS engine analyze..."));
13676     }
13677     if (first.analysisSupport && first.analyzing) {
13678       SendToProgram("exit\n", &first);
13679       first.analyzing = FALSE;
13680     }
13681     thinkOutput[0] = NULLCHAR;
13682 }
13683
13684 void
13685 EditPositionDone (Boolean fakeRights)
13686 {
13687     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13688
13689     startedFromSetupPosition = TRUE;
13690     InitChessProgram(&first, FALSE);
13691     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13692       boards[0][EP_STATUS] = EP_NONE;
13693       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13694     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13695         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13696         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13697       } else boards[0][CASTLING][2] = NoRights;
13698     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13699         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13700         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13701       } else boards[0][CASTLING][5] = NoRights;
13702     }
13703     SendToProgram("force\n", &first);
13704     if (blackPlaysFirst) {
13705         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13706         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13707         currentMove = forwardMostMove = backwardMostMove = 1;
13708         CopyBoard(boards[1], boards[0]);
13709     } else {
13710         currentMove = forwardMostMove = backwardMostMove = 0;
13711     }
13712     SendBoard(&first, forwardMostMove);
13713     if (appData.debugMode) {
13714         fprintf(debugFP, "EditPosDone\n");
13715     }
13716     DisplayTitle("");
13717     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13718     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13719     gameMode = EditGame;
13720     ModeHighlight();
13721     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13722     ClearHighlights(); /* [AS] */
13723 }
13724
13725 /* Pause for `ms' milliseconds */
13726 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13727 void
13728 TimeDelay (long ms)
13729 {
13730     TimeMark m1, m2;
13731
13732     GetTimeMark(&m1);
13733     do {
13734         GetTimeMark(&m2);
13735     } while (SubtractTimeMarks(&m2, &m1) < ms);
13736 }
13737
13738 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13739 void
13740 SendMultiLineToICS (char *buf)
13741 {
13742     char temp[MSG_SIZ+1], *p;
13743     int len;
13744
13745     len = strlen(buf);
13746     if (len > MSG_SIZ)
13747       len = MSG_SIZ;
13748
13749     strncpy(temp, buf, len);
13750     temp[len] = 0;
13751
13752     p = temp;
13753     while (*p) {
13754         if (*p == '\n' || *p == '\r')
13755           *p = ' ';
13756         ++p;
13757     }
13758
13759     strcat(temp, "\n");
13760     SendToICS(temp);
13761     SendToPlayer(temp, strlen(temp));
13762 }
13763
13764 void
13765 SetWhiteToPlayEvent ()
13766 {
13767     if (gameMode == EditPosition) {
13768         blackPlaysFirst = FALSE;
13769         DisplayBothClocks();    /* works because currentMove is 0 */
13770     } else if (gameMode == IcsExamining) {
13771         SendToICS(ics_prefix);
13772         SendToICS("tomove white\n");
13773     }
13774 }
13775
13776 void
13777 SetBlackToPlayEvent ()
13778 {
13779     if (gameMode == EditPosition) {
13780         blackPlaysFirst = TRUE;
13781         currentMove = 1;        /* kludge */
13782         DisplayBothClocks();
13783         currentMove = 0;
13784     } else if (gameMode == IcsExamining) {
13785         SendToICS(ics_prefix);
13786         SendToICS("tomove black\n");
13787     }
13788 }
13789
13790 void
13791 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13792 {
13793     char buf[MSG_SIZ];
13794     ChessSquare piece = boards[0][y][x];
13795
13796     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13797
13798     switch (selection) {
13799       case ClearBoard:
13800         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13801             SendToICS(ics_prefix);
13802             SendToICS("bsetup clear\n");
13803         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13804             SendToICS(ics_prefix);
13805             SendToICS("clearboard\n");
13806         } else {
13807             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13808                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13809                 for (y = 0; y < BOARD_HEIGHT; y++) {
13810                     if (gameMode == IcsExamining) {
13811                         if (boards[currentMove][y][x] != EmptySquare) {
13812                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13813                                     AAA + x, ONE + y);
13814                             SendToICS(buf);
13815                         }
13816                     } else {
13817                         boards[0][y][x] = p;
13818                     }
13819                 }
13820             }
13821         }
13822         if (gameMode == EditPosition) {
13823             DrawPosition(FALSE, boards[0]);
13824         }
13825         break;
13826
13827       case WhitePlay:
13828         SetWhiteToPlayEvent();
13829         break;
13830
13831       case BlackPlay:
13832         SetBlackToPlayEvent();
13833         break;
13834
13835       case EmptySquare:
13836         if (gameMode == IcsExamining) {
13837             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13838             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13839             SendToICS(buf);
13840         } else {
13841             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13842                 if(x == BOARD_LEFT-2) {
13843                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13844                     boards[0][y][1] = 0;
13845                 } else
13846                 if(x == BOARD_RGHT+1) {
13847                     if(y >= gameInfo.holdingsSize) break;
13848                     boards[0][y][BOARD_WIDTH-2] = 0;
13849                 } else break;
13850             }
13851             boards[0][y][x] = EmptySquare;
13852             DrawPosition(FALSE, boards[0]);
13853         }
13854         break;
13855
13856       case PromotePiece:
13857         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13858            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13859             selection = (ChessSquare) (PROMOTED piece);
13860         } else if(piece == EmptySquare) selection = WhiteSilver;
13861         else selection = (ChessSquare)((int)piece - 1);
13862         goto defaultlabel;
13863
13864       case DemotePiece:
13865         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13866            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13867             selection = (ChessSquare) (DEMOTED piece);
13868         } else if(piece == EmptySquare) selection = BlackSilver;
13869         else selection = (ChessSquare)((int)piece + 1);
13870         goto defaultlabel;
13871
13872       case WhiteQueen:
13873       case BlackQueen:
13874         if(gameInfo.variant == VariantShatranj ||
13875            gameInfo.variant == VariantXiangqi  ||
13876            gameInfo.variant == VariantCourier  ||
13877            gameInfo.variant == VariantMakruk     )
13878             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13879         goto defaultlabel;
13880
13881       case WhiteKing:
13882       case BlackKing:
13883         if(gameInfo.variant == VariantXiangqi)
13884             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13885         if(gameInfo.variant == VariantKnightmate)
13886             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13887       default:
13888         defaultlabel:
13889         if (gameMode == IcsExamining) {
13890             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13891             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13892                      PieceToChar(selection), AAA + x, ONE + y);
13893             SendToICS(buf);
13894         } else {
13895             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13896                 int n;
13897                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13898                     n = PieceToNumber(selection - BlackPawn);
13899                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13900                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13901                     boards[0][BOARD_HEIGHT-1-n][1]++;
13902                 } else
13903                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13904                     n = PieceToNumber(selection);
13905                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13906                     boards[0][n][BOARD_WIDTH-1] = selection;
13907                     boards[0][n][BOARD_WIDTH-2]++;
13908                 }
13909             } else
13910             boards[0][y][x] = selection;
13911             DrawPosition(TRUE, boards[0]);
13912         }
13913         break;
13914     }
13915 }
13916
13917
13918 void
13919 DropMenuEvent (ChessSquare selection, int x, int y)
13920 {
13921     ChessMove moveType;
13922
13923     switch (gameMode) {
13924       case IcsPlayingWhite:
13925       case MachinePlaysBlack:
13926         if (!WhiteOnMove(currentMove)) {
13927             DisplayMoveError(_("It is Black's turn"));
13928             return;
13929         }
13930         moveType = WhiteDrop;
13931         break;
13932       case IcsPlayingBlack:
13933       case MachinePlaysWhite:
13934         if (WhiteOnMove(currentMove)) {
13935             DisplayMoveError(_("It is White's turn"));
13936             return;
13937         }
13938         moveType = BlackDrop;
13939         break;
13940       case EditGame:
13941         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13942         break;
13943       default:
13944         return;
13945     }
13946
13947     if (moveType == BlackDrop && selection < BlackPawn) {
13948       selection = (ChessSquare) ((int) selection
13949                                  + (int) BlackPawn - (int) WhitePawn);
13950     }
13951     if (boards[currentMove][y][x] != EmptySquare) {
13952         DisplayMoveError(_("That square is occupied"));
13953         return;
13954     }
13955
13956     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13957 }
13958
13959 void
13960 AcceptEvent ()
13961 {
13962     /* Accept a pending offer of any kind from opponent */
13963
13964     if (appData.icsActive) {
13965         SendToICS(ics_prefix);
13966         SendToICS("accept\n");
13967     } else if (cmailMsgLoaded) {
13968         if (currentMove == cmailOldMove &&
13969             commentList[cmailOldMove] != NULL &&
13970             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13971                    "Black offers a draw" : "White offers a draw")) {
13972             TruncateGame();
13973             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13974             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13975         } else {
13976             DisplayError(_("There is no pending offer on this move"), 0);
13977             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13978         }
13979     } else {
13980         /* Not used for offers from chess program */
13981     }
13982 }
13983
13984 void
13985 DeclineEvent ()
13986 {
13987     /* Decline a pending offer of any kind from opponent */
13988
13989     if (appData.icsActive) {
13990         SendToICS(ics_prefix);
13991         SendToICS("decline\n");
13992     } else if (cmailMsgLoaded) {
13993         if (currentMove == cmailOldMove &&
13994             commentList[cmailOldMove] != NULL &&
13995             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13996                    "Black offers a draw" : "White offers a draw")) {
13997 #ifdef NOTDEF
13998             AppendComment(cmailOldMove, "Draw declined", TRUE);
13999             DisplayComment(cmailOldMove - 1, "Draw declined");
14000 #endif /*NOTDEF*/
14001         } else {
14002             DisplayError(_("There is no pending offer on this move"), 0);
14003         }
14004     } else {
14005         /* Not used for offers from chess program */
14006     }
14007 }
14008
14009 void
14010 RematchEvent ()
14011 {
14012     /* Issue ICS rematch command */
14013     if (appData.icsActive) {
14014         SendToICS(ics_prefix);
14015         SendToICS("rematch\n");
14016     }
14017 }
14018
14019 void
14020 CallFlagEvent ()
14021 {
14022     /* Call your opponent's flag (claim a win on time) */
14023     if (appData.icsActive) {
14024         SendToICS(ics_prefix);
14025         SendToICS("flag\n");
14026     } else {
14027         switch (gameMode) {
14028           default:
14029             return;
14030           case MachinePlaysWhite:
14031             if (whiteFlag) {
14032                 if (blackFlag)
14033                   GameEnds(GameIsDrawn, "Both players ran out of time",
14034                            GE_PLAYER);
14035                 else
14036                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14037             } else {
14038                 DisplayError(_("Your opponent is not out of time"), 0);
14039             }
14040             break;
14041           case MachinePlaysBlack:
14042             if (blackFlag) {
14043                 if (whiteFlag)
14044                   GameEnds(GameIsDrawn, "Both players ran out of time",
14045                            GE_PLAYER);
14046                 else
14047                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14048             } else {
14049                 DisplayError(_("Your opponent is not out of time"), 0);
14050             }
14051             break;
14052         }
14053     }
14054 }
14055
14056 void
14057 ClockClick (int which)
14058 {       // [HGM] code moved to back-end from winboard.c
14059         if(which) { // black clock
14060           if (gameMode == EditPosition || gameMode == IcsExamining) {
14061             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14062             SetBlackToPlayEvent();
14063           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14064           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14065           } else if (shiftKey) {
14066             AdjustClock(which, -1);
14067           } else if (gameMode == IcsPlayingWhite ||
14068                      gameMode == MachinePlaysBlack) {
14069             CallFlagEvent();
14070           }
14071         } else { // white clock
14072           if (gameMode == EditPosition || gameMode == IcsExamining) {
14073             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14074             SetWhiteToPlayEvent();
14075           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14076           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14077           } else if (shiftKey) {
14078             AdjustClock(which, -1);
14079           } else if (gameMode == IcsPlayingBlack ||
14080                    gameMode == MachinePlaysWhite) {
14081             CallFlagEvent();
14082           }
14083         }
14084 }
14085
14086 void
14087 DrawEvent ()
14088 {
14089     /* Offer draw or accept pending draw offer from opponent */
14090
14091     if (appData.icsActive) {
14092         /* Note: tournament rules require draw offers to be
14093            made after you make your move but before you punch
14094            your clock.  Currently ICS doesn't let you do that;
14095            instead, you immediately punch your clock after making
14096            a move, but you can offer a draw at any time. */
14097
14098         SendToICS(ics_prefix);
14099         SendToICS("draw\n");
14100         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14101     } else if (cmailMsgLoaded) {
14102         if (currentMove == cmailOldMove &&
14103             commentList[cmailOldMove] != NULL &&
14104             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14105                    "Black offers a draw" : "White offers a draw")) {
14106             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14107             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14108         } else if (currentMove == cmailOldMove + 1) {
14109             char *offer = WhiteOnMove(cmailOldMove) ?
14110               "White offers a draw" : "Black offers a draw";
14111             AppendComment(currentMove, offer, TRUE);
14112             DisplayComment(currentMove - 1, offer);
14113             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14114         } else {
14115             DisplayError(_("You must make your move before offering a draw"), 0);
14116             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14117         }
14118     } else if (first.offeredDraw) {
14119         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14120     } else {
14121         if (first.sendDrawOffers) {
14122             SendToProgram("draw\n", &first);
14123             userOfferedDraw = TRUE;
14124         }
14125     }
14126 }
14127
14128 void
14129 AdjournEvent ()
14130 {
14131     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14132
14133     if (appData.icsActive) {
14134         SendToICS(ics_prefix);
14135         SendToICS("adjourn\n");
14136     } else {
14137         /* Currently GNU Chess doesn't offer or accept Adjourns */
14138     }
14139 }
14140
14141
14142 void
14143 AbortEvent ()
14144 {
14145     /* Offer Abort or accept pending Abort offer from opponent */
14146
14147     if (appData.icsActive) {
14148         SendToICS(ics_prefix);
14149         SendToICS("abort\n");
14150     } else {
14151         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14152     }
14153 }
14154
14155 void
14156 ResignEvent ()
14157 {
14158     /* Resign.  You can do this even if it's not your turn. */
14159
14160     if (appData.icsActive) {
14161         SendToICS(ics_prefix);
14162         SendToICS("resign\n");
14163     } else {
14164         switch (gameMode) {
14165           case MachinePlaysWhite:
14166             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14167             break;
14168           case MachinePlaysBlack:
14169             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14170             break;
14171           case EditGame:
14172             if (cmailMsgLoaded) {
14173                 TruncateGame();
14174                 if (WhiteOnMove(cmailOldMove)) {
14175                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14176                 } else {
14177                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14178                 }
14179                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14180             }
14181             break;
14182           default:
14183             break;
14184         }
14185     }
14186 }
14187
14188
14189 void
14190 StopObservingEvent ()
14191 {
14192     /* Stop observing current games */
14193     SendToICS(ics_prefix);
14194     SendToICS("unobserve\n");
14195 }
14196
14197 void
14198 StopExaminingEvent ()
14199 {
14200     /* Stop observing current game */
14201     SendToICS(ics_prefix);
14202     SendToICS("unexamine\n");
14203 }
14204
14205 void
14206 ForwardInner (int target)
14207 {
14208     int limit; int oldSeekGraphUp = seekGraphUp;
14209
14210     if (appData.debugMode)
14211         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14212                 target, currentMove, forwardMostMove);
14213
14214     if (gameMode == EditPosition)
14215       return;
14216
14217     seekGraphUp = FALSE;
14218     MarkTargetSquares(1);
14219
14220     if (gameMode == PlayFromGameFile && !pausing)
14221       PauseEvent();
14222
14223     if (gameMode == IcsExamining && pausing)
14224       limit = pauseExamForwardMostMove;
14225     else
14226       limit = forwardMostMove;
14227
14228     if (target > limit) target = limit;
14229
14230     if (target > 0 && moveList[target - 1][0]) {
14231         int fromX, fromY, toX, toY;
14232         toX = moveList[target - 1][2] - AAA;
14233         toY = moveList[target - 1][3] - ONE;
14234         if (moveList[target - 1][1] == '@') {
14235             if (appData.highlightLastMove) {
14236                 SetHighlights(-1, -1, toX, toY);
14237             }
14238         } else {
14239             fromX = moveList[target - 1][0] - AAA;
14240             fromY = moveList[target - 1][1] - ONE;
14241             if (target == currentMove + 1) {
14242                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14243             }
14244             if (appData.highlightLastMove) {
14245                 SetHighlights(fromX, fromY, toX, toY);
14246             }
14247         }
14248     }
14249     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14250         gameMode == Training || gameMode == PlayFromGameFile ||
14251         gameMode == AnalyzeFile) {
14252         while (currentMove < target) {
14253             SendMoveToProgram(currentMove++, &first);
14254         }
14255     } else {
14256         currentMove = target;
14257     }
14258
14259     if (gameMode == EditGame || gameMode == EndOfGame) {
14260         whiteTimeRemaining = timeRemaining[0][currentMove];
14261         blackTimeRemaining = timeRemaining[1][currentMove];
14262     }
14263     DisplayBothClocks();
14264     DisplayMove(currentMove - 1);
14265     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14266     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14267     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14268         DisplayComment(currentMove - 1, commentList[currentMove]);
14269     }
14270 }
14271
14272
14273 void
14274 ForwardEvent ()
14275 {
14276     if (gameMode == IcsExamining && !pausing) {
14277         SendToICS(ics_prefix);
14278         SendToICS("forward\n");
14279     } else {
14280         ForwardInner(currentMove + 1);
14281     }
14282 }
14283
14284 void
14285 ToEndEvent ()
14286 {
14287     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14288         /* to optimze, we temporarily turn off analysis mode while we feed
14289          * the remaining moves to the engine. Otherwise we get analysis output
14290          * after each move.
14291          */
14292         if (first.analysisSupport) {
14293           SendToProgram("exit\nforce\n", &first);
14294           first.analyzing = FALSE;
14295         }
14296     }
14297
14298     if (gameMode == IcsExamining && !pausing) {
14299         SendToICS(ics_prefix);
14300         SendToICS("forward 999999\n");
14301     } else {
14302         ForwardInner(forwardMostMove);
14303     }
14304
14305     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14306         /* we have fed all the moves, so reactivate analysis mode */
14307         SendToProgram("analyze\n", &first);
14308         first.analyzing = TRUE;
14309         /*first.maybeThinking = TRUE;*/
14310         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14311     }
14312 }
14313
14314 void
14315 BackwardInner (int target)
14316 {
14317     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14318
14319     if (appData.debugMode)
14320         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14321                 target, currentMove, forwardMostMove);
14322
14323     if (gameMode == EditPosition) return;
14324     seekGraphUp = FALSE;
14325     MarkTargetSquares(1);
14326     if (currentMove <= backwardMostMove) {
14327         ClearHighlights();
14328         DrawPosition(full_redraw, boards[currentMove]);
14329         return;
14330     }
14331     if (gameMode == PlayFromGameFile && !pausing)
14332       PauseEvent();
14333
14334     if (moveList[target][0]) {
14335         int fromX, fromY, toX, toY;
14336         toX = moveList[target][2] - AAA;
14337         toY = moveList[target][3] - ONE;
14338         if (moveList[target][1] == '@') {
14339             if (appData.highlightLastMove) {
14340                 SetHighlights(-1, -1, toX, toY);
14341             }
14342         } else {
14343             fromX = moveList[target][0] - AAA;
14344             fromY = moveList[target][1] - ONE;
14345             if (target == currentMove - 1) {
14346                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14347             }
14348             if (appData.highlightLastMove) {
14349                 SetHighlights(fromX, fromY, toX, toY);
14350             }
14351         }
14352     }
14353     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14354         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14355         while (currentMove > target) {
14356             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14357                 // null move cannot be undone. Reload program with move history before it.
14358                 int i;
14359                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14360                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14361                 }
14362                 SendBoard(&first, i); 
14363                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14364                 break;
14365             }
14366             SendToProgram("undo\n", &first);
14367             currentMove--;
14368         }
14369     } else {
14370         currentMove = target;
14371     }
14372
14373     if (gameMode == EditGame || gameMode == EndOfGame) {
14374         whiteTimeRemaining = timeRemaining[0][currentMove];
14375         blackTimeRemaining = timeRemaining[1][currentMove];
14376     }
14377     DisplayBothClocks();
14378     DisplayMove(currentMove - 1);
14379     DrawPosition(full_redraw, boards[currentMove]);
14380     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14381     // [HGM] PV info: routine tests if comment empty
14382     DisplayComment(currentMove - 1, commentList[currentMove]);
14383 }
14384
14385 void
14386 BackwardEvent ()
14387 {
14388     if (gameMode == IcsExamining && !pausing) {
14389         SendToICS(ics_prefix);
14390         SendToICS("backward\n");
14391     } else {
14392         BackwardInner(currentMove - 1);
14393     }
14394 }
14395
14396 void
14397 ToStartEvent ()
14398 {
14399     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14400         /* to optimize, we temporarily turn off analysis mode while we undo
14401          * all the moves. Otherwise we get analysis output after each undo.
14402          */
14403         if (first.analysisSupport) {
14404           SendToProgram("exit\nforce\n", &first);
14405           first.analyzing = FALSE;
14406         }
14407     }
14408
14409     if (gameMode == IcsExamining && !pausing) {
14410         SendToICS(ics_prefix);
14411         SendToICS("backward 999999\n");
14412     } else {
14413         BackwardInner(backwardMostMove);
14414     }
14415
14416     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14417         /* we have fed all the moves, so reactivate analysis mode */
14418         SendToProgram("analyze\n", &first);
14419         first.analyzing = TRUE;
14420         /*first.maybeThinking = TRUE;*/
14421         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14422     }
14423 }
14424
14425 void
14426 ToNrEvent (int to)
14427 {
14428   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14429   if (to >= forwardMostMove) to = forwardMostMove;
14430   if (to <= backwardMostMove) to = backwardMostMove;
14431   if (to < currentMove) {
14432     BackwardInner(to);
14433   } else {
14434     ForwardInner(to);
14435   }
14436 }
14437
14438 void
14439 RevertEvent (Boolean annotate)
14440 {
14441     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14442         return;
14443     }
14444     if (gameMode != IcsExamining) {
14445         DisplayError(_("You are not examining a game"), 0);
14446         return;
14447     }
14448     if (pausing) {
14449         DisplayError(_("You can't revert while pausing"), 0);
14450         return;
14451     }
14452     SendToICS(ics_prefix);
14453     SendToICS("revert\n");
14454 }
14455
14456 void
14457 RetractMoveEvent ()
14458 {
14459     switch (gameMode) {
14460       case MachinePlaysWhite:
14461       case MachinePlaysBlack:
14462         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14463             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14464             return;
14465         }
14466         if (forwardMostMove < 2) return;
14467         currentMove = forwardMostMove = forwardMostMove - 2;
14468         whiteTimeRemaining = timeRemaining[0][currentMove];
14469         blackTimeRemaining = timeRemaining[1][currentMove];
14470         DisplayBothClocks();
14471         DisplayMove(currentMove - 1);
14472         ClearHighlights();/*!! could figure this out*/
14473         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14474         SendToProgram("remove\n", &first);
14475         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14476         break;
14477
14478       case BeginningOfGame:
14479       default:
14480         break;
14481
14482       case IcsPlayingWhite:
14483       case IcsPlayingBlack:
14484         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14485             SendToICS(ics_prefix);
14486             SendToICS("takeback 2\n");
14487         } else {
14488             SendToICS(ics_prefix);
14489             SendToICS("takeback 1\n");
14490         }
14491         break;
14492     }
14493 }
14494
14495 void
14496 MoveNowEvent ()
14497 {
14498     ChessProgramState *cps;
14499
14500     switch (gameMode) {
14501       case MachinePlaysWhite:
14502         if (!WhiteOnMove(forwardMostMove)) {
14503             DisplayError(_("It is your turn"), 0);
14504             return;
14505         }
14506         cps = &first;
14507         break;
14508       case MachinePlaysBlack:
14509         if (WhiteOnMove(forwardMostMove)) {
14510             DisplayError(_("It is your turn"), 0);
14511             return;
14512         }
14513         cps = &first;
14514         break;
14515       case TwoMachinesPlay:
14516         if (WhiteOnMove(forwardMostMove) ==
14517             (first.twoMachinesColor[0] == 'w')) {
14518             cps = &first;
14519         } else {
14520             cps = &second;
14521         }
14522         break;
14523       case BeginningOfGame:
14524       default:
14525         return;
14526     }
14527     SendToProgram("?\n", cps);
14528 }
14529
14530 void
14531 TruncateGameEvent ()
14532 {
14533     EditGameEvent();
14534     if (gameMode != EditGame) return;
14535     TruncateGame();
14536 }
14537
14538 void
14539 TruncateGame ()
14540 {
14541     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14542     if (forwardMostMove > currentMove) {
14543         if (gameInfo.resultDetails != NULL) {
14544             free(gameInfo.resultDetails);
14545             gameInfo.resultDetails = NULL;
14546             gameInfo.result = GameUnfinished;
14547         }
14548         forwardMostMove = currentMove;
14549         HistorySet(parseList, backwardMostMove, forwardMostMove,
14550                    currentMove-1);
14551     }
14552 }
14553
14554 void
14555 HintEvent ()
14556 {
14557     if (appData.noChessProgram) return;
14558     switch (gameMode) {
14559       case MachinePlaysWhite:
14560         if (WhiteOnMove(forwardMostMove)) {
14561             DisplayError(_("Wait until your turn"), 0);
14562             return;
14563         }
14564         break;
14565       case BeginningOfGame:
14566       case MachinePlaysBlack:
14567         if (!WhiteOnMove(forwardMostMove)) {
14568             DisplayError(_("Wait until your turn"), 0);
14569             return;
14570         }
14571         break;
14572       default:
14573         DisplayError(_("No hint available"), 0);
14574         return;
14575     }
14576     SendToProgram("hint\n", &first);
14577     hintRequested = TRUE;
14578 }
14579
14580 void
14581 BookEvent ()
14582 {
14583     if (appData.noChessProgram) return;
14584     switch (gameMode) {
14585       case MachinePlaysWhite:
14586         if (WhiteOnMove(forwardMostMove)) {
14587             DisplayError(_("Wait until your turn"), 0);
14588             return;
14589         }
14590         break;
14591       case BeginningOfGame:
14592       case MachinePlaysBlack:
14593         if (!WhiteOnMove(forwardMostMove)) {
14594             DisplayError(_("Wait until your turn"), 0);
14595             return;
14596         }
14597         break;
14598       case EditPosition:
14599         EditPositionDone(TRUE);
14600         break;
14601       case TwoMachinesPlay:
14602         return;
14603       default:
14604         break;
14605     }
14606     SendToProgram("bk\n", &first);
14607     bookOutput[0] = NULLCHAR;
14608     bookRequested = TRUE;
14609 }
14610
14611 void
14612 AboutGameEvent ()
14613 {
14614     char *tags = PGNTags(&gameInfo);
14615     TagsPopUp(tags, CmailMsg());
14616     free(tags);
14617 }
14618
14619 /* end button procedures */
14620
14621 void
14622 PrintPosition (FILE *fp, int move)
14623 {
14624     int i, j;
14625
14626     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14627         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14628             char c = PieceToChar(boards[move][i][j]);
14629             fputc(c == 'x' ? '.' : c, fp);
14630             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14631         }
14632     }
14633     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14634       fprintf(fp, "white to play\n");
14635     else
14636       fprintf(fp, "black to play\n");
14637 }
14638
14639 void
14640 PrintOpponents (FILE *fp)
14641 {
14642     if (gameInfo.white != NULL) {
14643         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14644     } else {
14645         fprintf(fp, "\n");
14646     }
14647 }
14648
14649 /* Find last component of program's own name, using some heuristics */
14650 void
14651 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14652 {
14653     char *p, *q, c;
14654     int local = (strcmp(host, "localhost") == 0);
14655     while (!local && (p = strchr(prog, ';')) != NULL) {
14656         p++;
14657         while (*p == ' ') p++;
14658         prog = p;
14659     }
14660     if (*prog == '"' || *prog == '\'') {
14661         q = strchr(prog + 1, *prog);
14662     } else {
14663         q = strchr(prog, ' ');
14664     }
14665     if (q == NULL) q = prog + strlen(prog);
14666     p = q;
14667     while (p >= prog && *p != '/' && *p != '\\') p--;
14668     p++;
14669     if(p == prog && *p == '"') p++;
14670     c = *q; *q = 0;
14671     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14672     memcpy(buf, p, q - p);
14673     buf[q - p] = NULLCHAR;
14674     if (!local) {
14675         strcat(buf, "@");
14676         strcat(buf, host);
14677     }
14678 }
14679
14680 char *
14681 TimeControlTagValue ()
14682 {
14683     char buf[MSG_SIZ];
14684     if (!appData.clockMode) {
14685       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14686     } else if (movesPerSession > 0) {
14687       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14688     } else if (timeIncrement == 0) {
14689       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14690     } else {
14691       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14692     }
14693     return StrSave(buf);
14694 }
14695
14696 void
14697 SetGameInfo ()
14698 {
14699     /* This routine is used only for certain modes */
14700     VariantClass v = gameInfo.variant;
14701     ChessMove r = GameUnfinished;
14702     char *p = NULL;
14703
14704     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14705         r = gameInfo.result;
14706         p = gameInfo.resultDetails;
14707         gameInfo.resultDetails = NULL;
14708     }
14709     ClearGameInfo(&gameInfo);
14710     gameInfo.variant = v;
14711
14712     switch (gameMode) {
14713       case MachinePlaysWhite:
14714         gameInfo.event = StrSave( appData.pgnEventHeader );
14715         gameInfo.site = StrSave(HostName());
14716         gameInfo.date = PGNDate();
14717         gameInfo.round = StrSave("-");
14718         gameInfo.white = StrSave(first.tidy);
14719         gameInfo.black = StrSave(UserName());
14720         gameInfo.timeControl = TimeControlTagValue();
14721         break;
14722
14723       case MachinePlaysBlack:
14724         gameInfo.event = StrSave( appData.pgnEventHeader );
14725         gameInfo.site = StrSave(HostName());
14726         gameInfo.date = PGNDate();
14727         gameInfo.round = StrSave("-");
14728         gameInfo.white = StrSave(UserName());
14729         gameInfo.black = StrSave(first.tidy);
14730         gameInfo.timeControl = TimeControlTagValue();
14731         break;
14732
14733       case TwoMachinesPlay:
14734         gameInfo.event = StrSave( appData.pgnEventHeader );
14735         gameInfo.site = StrSave(HostName());
14736         gameInfo.date = PGNDate();
14737         if (roundNr > 0) {
14738             char buf[MSG_SIZ];
14739             snprintf(buf, MSG_SIZ, "%d", roundNr);
14740             gameInfo.round = StrSave(buf);
14741         } else {
14742             gameInfo.round = StrSave("-");
14743         }
14744         if (first.twoMachinesColor[0] == 'w') {
14745             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14746             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14747         } else {
14748             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14749             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14750         }
14751         gameInfo.timeControl = TimeControlTagValue();
14752         break;
14753
14754       case EditGame:
14755         gameInfo.event = StrSave("Edited game");
14756         gameInfo.site = StrSave(HostName());
14757         gameInfo.date = PGNDate();
14758         gameInfo.round = StrSave("-");
14759         gameInfo.white = StrSave("-");
14760         gameInfo.black = StrSave("-");
14761         gameInfo.result = r;
14762         gameInfo.resultDetails = p;
14763         break;
14764
14765       case EditPosition:
14766         gameInfo.event = StrSave("Edited position");
14767         gameInfo.site = StrSave(HostName());
14768         gameInfo.date = PGNDate();
14769         gameInfo.round = StrSave("-");
14770         gameInfo.white = StrSave("-");
14771         gameInfo.black = StrSave("-");
14772         break;
14773
14774       case IcsPlayingWhite:
14775       case IcsPlayingBlack:
14776       case IcsObserving:
14777       case IcsExamining:
14778         break;
14779
14780       case PlayFromGameFile:
14781         gameInfo.event = StrSave("Game from non-PGN file");
14782         gameInfo.site = StrSave(HostName());
14783         gameInfo.date = PGNDate();
14784         gameInfo.round = StrSave("-");
14785         gameInfo.white = StrSave("?");
14786         gameInfo.black = StrSave("?");
14787         break;
14788
14789       default:
14790         break;
14791     }
14792 }
14793
14794 void
14795 ReplaceComment (int index, char *text)
14796 {
14797     int len;
14798     char *p;
14799     float score;
14800
14801     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14802        pvInfoList[index-1].depth == len &&
14803        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14804        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14805     while (*text == '\n') text++;
14806     len = strlen(text);
14807     while (len > 0 && text[len - 1] == '\n') len--;
14808
14809     if (commentList[index] != NULL)
14810       free(commentList[index]);
14811
14812     if (len == 0) {
14813         commentList[index] = NULL;
14814         return;
14815     }
14816   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14817       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14818       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14819     commentList[index] = (char *) malloc(len + 2);
14820     strncpy(commentList[index], text, len);
14821     commentList[index][len] = '\n';
14822     commentList[index][len + 1] = NULLCHAR;
14823   } else {
14824     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14825     char *p;
14826     commentList[index] = (char *) malloc(len + 7);
14827     safeStrCpy(commentList[index], "{\n", 3);
14828     safeStrCpy(commentList[index]+2, text, len+1);
14829     commentList[index][len+2] = NULLCHAR;
14830     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14831     strcat(commentList[index], "\n}\n");
14832   }
14833 }
14834
14835 void
14836 CrushCRs (char *text)
14837 {
14838   char *p = text;
14839   char *q = text;
14840   char ch;
14841
14842   do {
14843     ch = *p++;
14844     if (ch == '\r') continue;
14845     *q++ = ch;
14846   } while (ch != '\0');
14847 }
14848
14849 void
14850 AppendComment (int index, char *text, Boolean addBraces)
14851 /* addBraces  tells if we should add {} */
14852 {
14853     int oldlen, len;
14854     char *old;
14855
14856 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14857     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14858
14859     CrushCRs(text);
14860     while (*text == '\n') text++;
14861     len = strlen(text);
14862     while (len > 0 && text[len - 1] == '\n') len--;
14863     text[len] = NULLCHAR;
14864
14865     if (len == 0) return;
14866
14867     if (commentList[index] != NULL) {
14868       Boolean addClosingBrace = addBraces;
14869         old = commentList[index];
14870         oldlen = strlen(old);
14871         while(commentList[index][oldlen-1] ==  '\n')
14872           commentList[index][--oldlen] = NULLCHAR;
14873         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14874         safeStrCpy(commentList[index], old, oldlen + len + 6);
14875         free(old);
14876         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14877         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14878           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14879           while (*text == '\n') { text++; len--; }
14880           commentList[index][--oldlen] = NULLCHAR;
14881       }
14882         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14883         else          strcat(commentList[index], "\n");
14884         strcat(commentList[index], text);
14885         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14886         else          strcat(commentList[index], "\n");
14887     } else {
14888         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14889         if(addBraces)
14890           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14891         else commentList[index][0] = NULLCHAR;
14892         strcat(commentList[index], text);
14893         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14894         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14895     }
14896 }
14897
14898 static char *
14899 FindStr (char * text, char * sub_text)
14900 {
14901     char * result = strstr( text, sub_text );
14902
14903     if( result != NULL ) {
14904         result += strlen( sub_text );
14905     }
14906
14907     return result;
14908 }
14909
14910 /* [AS] Try to extract PV info from PGN comment */
14911 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14912 char *
14913 GetInfoFromComment (int index, char * text)
14914 {
14915     char * sep = text, *p;
14916
14917     if( text != NULL && index > 0 ) {
14918         int score = 0;
14919         int depth = 0;
14920         int time = -1, sec = 0, deci;
14921         char * s_eval = FindStr( text, "[%eval " );
14922         char * s_emt = FindStr( text, "[%emt " );
14923
14924         if( s_eval != NULL || s_emt != NULL ) {
14925             /* New style */
14926             char delim;
14927
14928             if( s_eval != NULL ) {
14929                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14930                     return text;
14931                 }
14932
14933                 if( delim != ']' ) {
14934                     return text;
14935                 }
14936             }
14937
14938             if( s_emt != NULL ) {
14939             }
14940                 return text;
14941         }
14942         else {
14943             /* We expect something like: [+|-]nnn.nn/dd */
14944             int score_lo = 0;
14945
14946             if(*text != '{') return text; // [HGM] braces: must be normal comment
14947
14948             sep = strchr( text, '/' );
14949             if( sep == NULL || sep < (text+4) ) {
14950                 return text;
14951             }
14952
14953             p = text;
14954             if(p[1] == '(') { // comment starts with PV
14955                p = strchr(p, ')'); // locate end of PV
14956                if(p == NULL || sep < p+5) return text;
14957                // at this point we have something like "{(.*) +0.23/6 ..."
14958                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14959                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14960                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14961             }
14962             time = -1; sec = -1; deci = -1;
14963             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14964                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14965                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14966                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14967                 return text;
14968             }
14969
14970             if( score_lo < 0 || score_lo >= 100 ) {
14971                 return text;
14972             }
14973
14974             if(sec >= 0) time = 600*time + 10*sec; else
14975             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14976
14977             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14978
14979             /* [HGM] PV time: now locate end of PV info */
14980             while( *++sep >= '0' && *sep <= '9'); // strip depth
14981             if(time >= 0)
14982             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14983             if(sec >= 0)
14984             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14985             if(deci >= 0)
14986             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14987             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
14988         }
14989
14990         if( depth <= 0 ) {
14991             return text;
14992         }
14993
14994         if( time < 0 ) {
14995             time = -1;
14996         }
14997
14998         pvInfoList[index-1].depth = depth;
14999         pvInfoList[index-1].score = score;
15000         pvInfoList[index-1].time  = 10*time; // centi-sec
15001         if(*sep == '}') *sep = 0; else *--sep = '{';
15002         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15003     }
15004     return sep;
15005 }
15006
15007 void
15008 SendToProgram (char *message, ChessProgramState *cps)
15009 {
15010     int count, outCount, error;
15011     char buf[MSG_SIZ];
15012
15013     if (cps->pr == NoProc) return;
15014     Attention(cps);
15015
15016     if (appData.debugMode) {
15017         TimeMark now;
15018         GetTimeMark(&now);
15019         fprintf(debugFP, "%ld >%-6s: %s",
15020                 SubtractTimeMarks(&now, &programStartTime),
15021                 cps->which, message);
15022     }
15023
15024     count = strlen(message);
15025     outCount = OutputToProcess(cps->pr, message, count, &error);
15026     if (outCount < count && !exiting
15027                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15028       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15029       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15030         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15031             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15032                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15033                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15034                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15035             } else {
15036                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15037                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15038                 gameInfo.result = res;
15039             }
15040             gameInfo.resultDetails = StrSave(buf);
15041         }
15042         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15043         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15044     }
15045 }
15046
15047 void
15048 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15049 {
15050     char *end_str;
15051     char buf[MSG_SIZ];
15052     ChessProgramState *cps = (ChessProgramState *)closure;
15053
15054     if (isr != cps->isr) return; /* Killed intentionally */
15055     if (count <= 0) {
15056         if (count == 0) {
15057             RemoveInputSource(cps->isr);
15058             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15059             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15060                     _(cps->which), cps->program);
15061         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15062                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15063                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15064                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15065                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15066                 } else {
15067                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15068                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15069                     gameInfo.result = res;
15070                 }
15071                 gameInfo.resultDetails = StrSave(buf);
15072             }
15073             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15074             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15075         } else {
15076             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15077                     _(cps->which), cps->program);
15078             RemoveInputSource(cps->isr);
15079
15080             /* [AS] Program is misbehaving badly... kill it */
15081             if( count == -2 ) {
15082                 DestroyChildProcess( cps->pr, 9 );
15083                 cps->pr = NoProc;
15084             }
15085
15086             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15087         }
15088         return;
15089     }
15090
15091     if ((end_str = strchr(message, '\r')) != NULL)
15092       *end_str = NULLCHAR;
15093     if ((end_str = strchr(message, '\n')) != NULL)
15094       *end_str = NULLCHAR;
15095
15096     if (appData.debugMode) {
15097         TimeMark now; int print = 1;
15098         char *quote = ""; char c; int i;
15099
15100         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15101                 char start = message[0];
15102                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15103                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15104                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15105                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15106                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15107                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15108                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15109                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15110                    sscanf(message, "hint: %c", &c)!=1 && 
15111                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15112                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15113                     print = (appData.engineComments >= 2);
15114                 }
15115                 message[0] = start; // restore original message
15116         }
15117         if(print) {
15118                 GetTimeMark(&now);
15119                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15120                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15121                         quote,
15122                         message);
15123         }
15124     }
15125
15126     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15127     if (appData.icsEngineAnalyze) {
15128         if (strstr(message, "whisper") != NULL ||
15129              strstr(message, "kibitz") != NULL ||
15130             strstr(message, "tellics") != NULL) return;
15131     }
15132
15133     HandleMachineMove(message, cps);
15134 }
15135
15136
15137 void
15138 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15139 {
15140     char buf[MSG_SIZ];
15141     int seconds;
15142
15143     if( timeControl_2 > 0 ) {
15144         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15145             tc = timeControl_2;
15146         }
15147     }
15148     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15149     inc /= cps->timeOdds;
15150     st  /= cps->timeOdds;
15151
15152     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15153
15154     if (st > 0) {
15155       /* Set exact time per move, normally using st command */
15156       if (cps->stKludge) {
15157         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15158         seconds = st % 60;
15159         if (seconds == 0) {
15160           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15161         } else {
15162           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15163         }
15164       } else {
15165         snprintf(buf, MSG_SIZ, "st %d\n", st);
15166       }
15167     } else {
15168       /* Set conventional or incremental time control, using level command */
15169       if (seconds == 0) {
15170         /* Note old gnuchess bug -- minutes:seconds used to not work.
15171            Fixed in later versions, but still avoid :seconds
15172            when seconds is 0. */
15173         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15174       } else {
15175         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15176                  seconds, inc/1000.);
15177       }
15178     }
15179     SendToProgram(buf, cps);
15180
15181     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15182     /* Orthogonally, limit search to given depth */
15183     if (sd > 0) {
15184       if (cps->sdKludge) {
15185         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15186       } else {
15187         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15188       }
15189       SendToProgram(buf, cps);
15190     }
15191
15192     if(cps->nps >= 0) { /* [HGM] nps */
15193         if(cps->supportsNPS == FALSE)
15194           cps->nps = -1; // don't use if engine explicitly says not supported!
15195         else {
15196           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15197           SendToProgram(buf, cps);
15198         }
15199     }
15200 }
15201
15202 ChessProgramState *
15203 WhitePlayer ()
15204 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15205 {
15206     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15207        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15208         return &second;
15209     return &first;
15210 }
15211
15212 void
15213 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15214 {
15215     char message[MSG_SIZ];
15216     long time, otime;
15217
15218     /* Note: this routine must be called when the clocks are stopped
15219        or when they have *just* been set or switched; otherwise
15220        it will be off by the time since the current tick started.
15221     */
15222     if (machineWhite) {
15223         time = whiteTimeRemaining / 10;
15224         otime = blackTimeRemaining / 10;
15225     } else {
15226         time = blackTimeRemaining / 10;
15227         otime = whiteTimeRemaining / 10;
15228     }
15229     /* [HGM] translate opponent's time by time-odds factor */
15230     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15231
15232     if (time <= 0) time = 1;
15233     if (otime <= 0) otime = 1;
15234
15235     snprintf(message, MSG_SIZ, "time %ld\n", time);
15236     SendToProgram(message, cps);
15237
15238     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15239     SendToProgram(message, cps);
15240 }
15241
15242 int
15243 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15244 {
15245   char buf[MSG_SIZ];
15246   int len = strlen(name);
15247   int val;
15248
15249   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15250     (*p) += len + 1;
15251     sscanf(*p, "%d", &val);
15252     *loc = (val != 0);
15253     while (**p && **p != ' ')
15254       (*p)++;
15255     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15256     SendToProgram(buf, cps);
15257     return TRUE;
15258   }
15259   return FALSE;
15260 }
15261
15262 int
15263 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15264 {
15265   char buf[MSG_SIZ];
15266   int len = strlen(name);
15267   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15268     (*p) += len + 1;
15269     sscanf(*p, "%d", loc);
15270     while (**p && **p != ' ') (*p)++;
15271     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15272     SendToProgram(buf, cps);
15273     return TRUE;
15274   }
15275   return FALSE;
15276 }
15277
15278 int
15279 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15280 {
15281   char buf[MSG_SIZ];
15282   int len = strlen(name);
15283   if (strncmp((*p), name, len) == 0
15284       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15285     (*p) += len + 2;
15286     sscanf(*p, "%[^\"]", loc);
15287     while (**p && **p != '\"') (*p)++;
15288     if (**p == '\"') (*p)++;
15289     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15290     SendToProgram(buf, cps);
15291     return TRUE;
15292   }
15293   return FALSE;
15294 }
15295
15296 int
15297 ParseOption (Option *opt, ChessProgramState *cps)
15298 // [HGM] options: process the string that defines an engine option, and determine
15299 // name, type, default value, and allowed value range
15300 {
15301         char *p, *q, buf[MSG_SIZ];
15302         int n, min = (-1)<<31, max = 1<<31, def;
15303
15304         if(p = strstr(opt->name, " -spin ")) {
15305             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15306             if(max < min) max = min; // enforce consistency
15307             if(def < min) def = min;
15308             if(def > max) def = max;
15309             opt->value = def;
15310             opt->min = min;
15311             opt->max = max;
15312             opt->type = Spin;
15313         } else if((p = strstr(opt->name, " -slider "))) {
15314             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15315             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15316             if(max < min) max = min; // enforce consistency
15317             if(def < min) def = min;
15318             if(def > max) def = max;
15319             opt->value = def;
15320             opt->min = min;
15321             opt->max = max;
15322             opt->type = Spin; // Slider;
15323         } else if((p = strstr(opt->name, " -string "))) {
15324             opt->textValue = p+9;
15325             opt->type = TextBox;
15326         } else if((p = strstr(opt->name, " -file "))) {
15327             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15328             opt->textValue = p+7;
15329             opt->type = FileName; // FileName;
15330         } else if((p = strstr(opt->name, " -path "))) {
15331             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15332             opt->textValue = p+7;
15333             opt->type = PathName; // PathName;
15334         } else if(p = strstr(opt->name, " -check ")) {
15335             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15336             opt->value = (def != 0);
15337             opt->type = CheckBox;
15338         } else if(p = strstr(opt->name, " -combo ")) {
15339             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15340             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15341             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15342             opt->value = n = 0;
15343             while(q = StrStr(q, " /// ")) {
15344                 n++; *q = 0;    // count choices, and null-terminate each of them
15345                 q += 5;
15346                 if(*q == '*') { // remember default, which is marked with * prefix
15347                     q++;
15348                     opt->value = n;
15349                 }
15350                 cps->comboList[cps->comboCnt++] = q;
15351             }
15352             cps->comboList[cps->comboCnt++] = NULL;
15353             opt->max = n + 1;
15354             opt->type = ComboBox;
15355         } else if(p = strstr(opt->name, " -button")) {
15356             opt->type = Button;
15357         } else if(p = strstr(opt->name, " -save")) {
15358             opt->type = SaveButton;
15359         } else return FALSE;
15360         *p = 0; // terminate option name
15361         // now look if the command-line options define a setting for this engine option.
15362         if(cps->optionSettings && cps->optionSettings[0])
15363             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15364         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15365           snprintf(buf, MSG_SIZ, "option %s", p);
15366                 if(p = strstr(buf, ",")) *p = 0;
15367                 if(q = strchr(buf, '=')) switch(opt->type) {
15368                     case ComboBox:
15369                         for(n=0; n<opt->max; n++)
15370                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15371                         break;
15372                     case TextBox:
15373                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15374                         break;
15375                     case Spin:
15376                     case CheckBox:
15377                         opt->value = atoi(q+1);
15378                     default:
15379                         break;
15380                 }
15381                 strcat(buf, "\n");
15382                 SendToProgram(buf, cps);
15383         }
15384         return TRUE;
15385 }
15386
15387 void
15388 FeatureDone (ChessProgramState *cps, int val)
15389 {
15390   DelayedEventCallback cb = GetDelayedEvent();
15391   if ((cb == InitBackEnd3 && cps == &first) ||
15392       (cb == SettingsMenuIfReady && cps == &second) ||
15393       (cb == LoadEngine) ||
15394       (cb == TwoMachinesEventIfReady)) {
15395     CancelDelayedEvent();
15396     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15397   }
15398   cps->initDone = val;
15399 }
15400
15401 /* Parse feature command from engine */
15402 void
15403 ParseFeatures (char *args, ChessProgramState *cps)
15404 {
15405   char *p = args;
15406   char *q;
15407   int val;
15408   char buf[MSG_SIZ];
15409
15410   for (;;) {
15411     while (*p == ' ') p++;
15412     if (*p == NULLCHAR) return;
15413
15414     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15415     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15416     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15417     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15418     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15419     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15420     if (BoolFeature(&p, "reuse", &val, cps)) {
15421       /* Engine can disable reuse, but can't enable it if user said no */
15422       if (!val) cps->reuse = FALSE;
15423       continue;
15424     }
15425     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15426     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15427       if (gameMode == TwoMachinesPlay) {
15428         DisplayTwoMachinesTitle();
15429       } else {
15430         DisplayTitle("");
15431       }
15432       continue;
15433     }
15434     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15435     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15436     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15437     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15438     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15439     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15440     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15441     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15442     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15443     if (IntFeature(&p, "done", &val, cps)) {
15444       FeatureDone(cps, val);
15445       continue;
15446     }
15447     /* Added by Tord: */
15448     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15449     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15450     /* End of additions by Tord */
15451
15452     /* [HGM] added features: */
15453     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15454     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15455     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15456     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15457     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15458     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15459     if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15460         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15461           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15462             SendToProgram(buf, cps);
15463             continue;
15464         }
15465         if(cps->nrOptions >= MAX_OPTIONS) {
15466             cps->nrOptions--;
15467             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15468             DisplayError(buf, 0);
15469         }
15470         continue;
15471     }
15472     /* End of additions by HGM */
15473
15474     /* unknown feature: complain and skip */
15475     q = p;
15476     while (*q && *q != '=') q++;
15477     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15478     SendToProgram(buf, cps);
15479     p = q;
15480     if (*p == '=') {
15481       p++;
15482       if (*p == '\"') {
15483         p++;
15484         while (*p && *p != '\"') p++;
15485         if (*p == '\"') p++;
15486       } else {
15487         while (*p && *p != ' ') p++;
15488       }
15489     }
15490   }
15491
15492 }
15493
15494 void
15495 PeriodicUpdatesEvent (int newState)
15496 {
15497     if (newState == appData.periodicUpdates)
15498       return;
15499
15500     appData.periodicUpdates=newState;
15501
15502     /* Display type changes, so update it now */
15503 //    DisplayAnalysis();
15504
15505     /* Get the ball rolling again... */
15506     if (newState) {
15507         AnalysisPeriodicEvent(1);
15508         StartAnalysisClock();
15509     }
15510 }
15511
15512 void
15513 PonderNextMoveEvent (int newState)
15514 {
15515     if (newState == appData.ponderNextMove) return;
15516     if (gameMode == EditPosition) EditPositionDone(TRUE);
15517     if (newState) {
15518         SendToProgram("hard\n", &first);
15519         if (gameMode == TwoMachinesPlay) {
15520             SendToProgram("hard\n", &second);
15521         }
15522     } else {
15523         SendToProgram("easy\n", &first);
15524         thinkOutput[0] = NULLCHAR;
15525         if (gameMode == TwoMachinesPlay) {
15526             SendToProgram("easy\n", &second);
15527         }
15528     }
15529     appData.ponderNextMove = newState;
15530 }
15531
15532 void
15533 NewSettingEvent (int option, int *feature, char *command, int value)
15534 {
15535     char buf[MSG_SIZ];
15536
15537     if (gameMode == EditPosition) EditPositionDone(TRUE);
15538     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15539     if(feature == NULL || *feature) SendToProgram(buf, &first);
15540     if (gameMode == TwoMachinesPlay) {
15541         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15542     }
15543 }
15544
15545 void
15546 ShowThinkingEvent ()
15547 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15548 {
15549     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15550     int newState = appData.showThinking
15551         // [HGM] thinking: other features now need thinking output as well
15552         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15553
15554     if (oldState == newState) return;
15555     oldState = newState;
15556     if (gameMode == EditPosition) EditPositionDone(TRUE);
15557     if (oldState) {
15558         SendToProgram("post\n", &first);
15559         if (gameMode == TwoMachinesPlay) {
15560             SendToProgram("post\n", &second);
15561         }
15562     } else {
15563         SendToProgram("nopost\n", &first);
15564         thinkOutput[0] = NULLCHAR;
15565         if (gameMode == TwoMachinesPlay) {
15566             SendToProgram("nopost\n", &second);
15567         }
15568     }
15569 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15570 }
15571
15572 void
15573 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15574 {
15575   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15576   if (pr == NoProc) return;
15577   AskQuestion(title, question, replyPrefix, pr);
15578 }
15579
15580 void
15581 TypeInEvent (char firstChar)
15582 {
15583     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15584         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15585         gameMode == AnalyzeMode || gameMode == EditGame || 
15586         gameMode == EditPosition || gameMode == IcsExamining ||
15587         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15588         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15589                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15590                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15591         gameMode == Training) PopUpMoveDialog(firstChar);
15592 }
15593
15594 void
15595 TypeInDoneEvent (char *move)
15596 {
15597         Board board;
15598         int n, fromX, fromY, toX, toY;
15599         char promoChar;
15600         ChessMove moveType;
15601
15602         // [HGM] FENedit
15603         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15604                 EditPositionPasteFEN(move);
15605                 return;
15606         }
15607         // [HGM] movenum: allow move number to be typed in any mode
15608         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15609           ToNrEvent(2*n-1);
15610           return;
15611         }
15612         // undocumented kludge: allow command-line option to be typed in!
15613         // (potentially fatal, and does not implement the effect of the option.)
15614         // should only be used for options that are values on which future decisions will be made,
15615         // and definitely not on options that would be used during initialization.
15616         if(strstr(move, "!!! -") == move) {
15617             ParseArgsFromString(move+4);
15618             return;
15619         }
15620
15621       if (gameMode != EditGame && currentMove != forwardMostMove && 
15622         gameMode != Training) {
15623         DisplayMoveError(_("Displayed move is not current"));
15624       } else {
15625         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15626           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15627         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15628         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15629           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15630           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15631         } else {
15632           DisplayMoveError(_("Could not parse move"));
15633         }
15634       }
15635 }
15636
15637 void
15638 DisplayMove (int moveNumber)
15639 {
15640     char message[MSG_SIZ];
15641     char res[MSG_SIZ];
15642     char cpThinkOutput[MSG_SIZ];
15643
15644     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15645
15646     if (moveNumber == forwardMostMove - 1 ||
15647         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15648
15649         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15650
15651         if (strchr(cpThinkOutput, '\n')) {
15652             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15653         }
15654     } else {
15655         *cpThinkOutput = NULLCHAR;
15656     }
15657
15658     /* [AS] Hide thinking from human user */
15659     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15660         *cpThinkOutput = NULLCHAR;
15661         if( thinkOutput[0] != NULLCHAR ) {
15662             int i;
15663
15664             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15665                 cpThinkOutput[i] = '.';
15666             }
15667             cpThinkOutput[i] = NULLCHAR;
15668             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15669         }
15670     }
15671
15672     if (moveNumber == forwardMostMove - 1 &&
15673         gameInfo.resultDetails != NULL) {
15674         if (gameInfo.resultDetails[0] == NULLCHAR) {
15675           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15676         } else {
15677           snprintf(res, MSG_SIZ, " {%s} %s",
15678                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15679         }
15680     } else {
15681         res[0] = NULLCHAR;
15682     }
15683
15684     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15685         DisplayMessage(res, cpThinkOutput);
15686     } else {
15687       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15688                 WhiteOnMove(moveNumber) ? " " : ".. ",
15689                 parseList[moveNumber], res);
15690         DisplayMessage(message, cpThinkOutput);
15691     }
15692 }
15693
15694 void
15695 DisplayComment (int moveNumber, char *text)
15696 {
15697     char title[MSG_SIZ];
15698
15699     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15700       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15701     } else {
15702       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15703               WhiteOnMove(moveNumber) ? " " : ".. ",
15704               parseList[moveNumber]);
15705     }
15706     if (text != NULL && (appData.autoDisplayComment || commentUp))
15707         CommentPopUp(title, text);
15708 }
15709
15710 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15711  * might be busy thinking or pondering.  It can be omitted if your
15712  * gnuchess is configured to stop thinking immediately on any user
15713  * input.  However, that gnuchess feature depends on the FIONREAD
15714  * ioctl, which does not work properly on some flavors of Unix.
15715  */
15716 void
15717 Attention (ChessProgramState *cps)
15718 {
15719 #if ATTENTION
15720     if (!cps->useSigint) return;
15721     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15722     switch (gameMode) {
15723       case MachinePlaysWhite:
15724       case MachinePlaysBlack:
15725       case TwoMachinesPlay:
15726       case IcsPlayingWhite:
15727       case IcsPlayingBlack:
15728       case AnalyzeMode:
15729       case AnalyzeFile:
15730         /* Skip if we know it isn't thinking */
15731         if (!cps->maybeThinking) return;
15732         if (appData.debugMode)
15733           fprintf(debugFP, "Interrupting %s\n", cps->which);
15734         InterruptChildProcess(cps->pr);
15735         cps->maybeThinking = FALSE;
15736         break;
15737       default:
15738         break;
15739     }
15740 #endif /*ATTENTION*/
15741 }
15742
15743 int
15744 CheckFlags ()
15745 {
15746     if (whiteTimeRemaining <= 0) {
15747         if (!whiteFlag) {
15748             whiteFlag = TRUE;
15749             if (appData.icsActive) {
15750                 if (appData.autoCallFlag &&
15751                     gameMode == IcsPlayingBlack && !blackFlag) {
15752                   SendToICS(ics_prefix);
15753                   SendToICS("flag\n");
15754                 }
15755             } else {
15756                 if (blackFlag) {
15757                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15758                 } else {
15759                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15760                     if (appData.autoCallFlag) {
15761                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15762                         return TRUE;
15763                     }
15764                 }
15765             }
15766         }
15767     }
15768     if (blackTimeRemaining <= 0) {
15769         if (!blackFlag) {
15770             blackFlag = TRUE;
15771             if (appData.icsActive) {
15772                 if (appData.autoCallFlag &&
15773                     gameMode == IcsPlayingWhite && !whiteFlag) {
15774                   SendToICS(ics_prefix);
15775                   SendToICS("flag\n");
15776                 }
15777             } else {
15778                 if (whiteFlag) {
15779                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15780                 } else {
15781                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15782                     if (appData.autoCallFlag) {
15783                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15784                         return TRUE;
15785                     }
15786                 }
15787             }
15788         }
15789     }
15790     return FALSE;
15791 }
15792
15793 void
15794 CheckTimeControl ()
15795 {
15796     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15797         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15798
15799     /*
15800      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15801      */
15802     if ( !WhiteOnMove(forwardMostMove) ) {
15803         /* White made time control */
15804         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15805         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15806         /* [HGM] time odds: correct new time quota for time odds! */
15807                                             / WhitePlayer()->timeOdds;
15808         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15809     } else {
15810         lastBlack -= blackTimeRemaining;
15811         /* Black made time control */
15812         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15813                                             / WhitePlayer()->other->timeOdds;
15814         lastWhite = whiteTimeRemaining;
15815     }
15816 }
15817
15818 void
15819 DisplayBothClocks ()
15820 {
15821     int wom = gameMode == EditPosition ?
15822       !blackPlaysFirst : WhiteOnMove(currentMove);
15823     DisplayWhiteClock(whiteTimeRemaining, wom);
15824     DisplayBlackClock(blackTimeRemaining, !wom);
15825 }
15826
15827
15828 /* Timekeeping seems to be a portability nightmare.  I think everyone
15829    has ftime(), but I'm really not sure, so I'm including some ifdefs
15830    to use other calls if you don't.  Clocks will be less accurate if
15831    you have neither ftime nor gettimeofday.
15832 */
15833
15834 /* VS 2008 requires the #include outside of the function */
15835 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15836 #include <sys/timeb.h>
15837 #endif
15838
15839 /* Get the current time as a TimeMark */
15840 void
15841 GetTimeMark (TimeMark *tm)
15842 {
15843 #if HAVE_GETTIMEOFDAY
15844
15845     struct timeval timeVal;
15846     struct timezone timeZone;
15847
15848     gettimeofday(&timeVal, &timeZone);
15849     tm->sec = (long) timeVal.tv_sec;
15850     tm->ms = (int) (timeVal.tv_usec / 1000L);
15851
15852 #else /*!HAVE_GETTIMEOFDAY*/
15853 #if HAVE_FTIME
15854
15855 // include <sys/timeb.h> / moved to just above start of function
15856     struct timeb timeB;
15857
15858     ftime(&timeB);
15859     tm->sec = (long) timeB.time;
15860     tm->ms = (int) timeB.millitm;
15861
15862 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15863     tm->sec = (long) time(NULL);
15864     tm->ms = 0;
15865 #endif
15866 #endif
15867 }
15868
15869 /* Return the difference in milliseconds between two
15870    time marks.  We assume the difference will fit in a long!
15871 */
15872 long
15873 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
15874 {
15875     return 1000L*(tm2->sec - tm1->sec) +
15876            (long) (tm2->ms - tm1->ms);
15877 }
15878
15879
15880 /*
15881  * Code to manage the game clocks.
15882  *
15883  * In tournament play, black starts the clock and then white makes a move.
15884  * We give the human user a slight advantage if he is playing white---the
15885  * clocks don't run until he makes his first move, so it takes zero time.
15886  * Also, we don't account for network lag, so we could get out of sync
15887  * with GNU Chess's clock -- but then, referees are always right.
15888  */
15889
15890 static TimeMark tickStartTM;
15891 static long intendedTickLength;
15892
15893 long
15894 NextTickLength (long timeRemaining)
15895 {
15896     long nominalTickLength, nextTickLength;
15897
15898     if (timeRemaining > 0L && timeRemaining <= 10000L)
15899       nominalTickLength = 100L;
15900     else
15901       nominalTickLength = 1000L;
15902     nextTickLength = timeRemaining % nominalTickLength;
15903     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15904
15905     return nextTickLength;
15906 }
15907
15908 /* Adjust clock one minute up or down */
15909 void
15910 AdjustClock (Boolean which, int dir)
15911 {
15912     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15913     if(which) blackTimeRemaining += 60000*dir;
15914     else      whiteTimeRemaining += 60000*dir;
15915     DisplayBothClocks();
15916     adjustedClock = TRUE;
15917 }
15918
15919 /* Stop clocks and reset to a fresh time control */
15920 void
15921 ResetClocks ()
15922 {
15923     (void) StopClockTimer();
15924     if (appData.icsActive) {
15925         whiteTimeRemaining = blackTimeRemaining = 0;
15926     } else if (searchTime) {
15927         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15928         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15929     } else { /* [HGM] correct new time quote for time odds */
15930         whiteTC = blackTC = fullTimeControlString;
15931         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15932         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15933     }
15934     if (whiteFlag || blackFlag) {
15935         DisplayTitle("");
15936         whiteFlag = blackFlag = FALSE;
15937     }
15938     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15939     DisplayBothClocks();
15940     adjustedClock = FALSE;
15941 }
15942
15943 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15944
15945 /* Decrement running clock by amount of time that has passed */
15946 void
15947 DecrementClocks ()
15948 {
15949     long timeRemaining;
15950     long lastTickLength, fudge;
15951     TimeMark now;
15952
15953     if (!appData.clockMode) return;
15954     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15955
15956     GetTimeMark(&now);
15957
15958     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15959
15960     /* Fudge if we woke up a little too soon */
15961     fudge = intendedTickLength - lastTickLength;
15962     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15963
15964     if (WhiteOnMove(forwardMostMove)) {
15965         if(whiteNPS >= 0) lastTickLength = 0;
15966         timeRemaining = whiteTimeRemaining -= lastTickLength;
15967         if(timeRemaining < 0 && !appData.icsActive) {
15968             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15969             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15970                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15971                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15972             }
15973         }
15974         DisplayWhiteClock(whiteTimeRemaining - fudge,
15975                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15976     } else {
15977         if(blackNPS >= 0) lastTickLength = 0;
15978         timeRemaining = blackTimeRemaining -= lastTickLength;
15979         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15980             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15981             if(suddenDeath) {
15982                 blackStartMove = forwardMostMove;
15983                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15984             }
15985         }
15986         DisplayBlackClock(blackTimeRemaining - fudge,
15987                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15988     }
15989     if (CheckFlags()) return;
15990
15991     tickStartTM = now;
15992     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15993     StartClockTimer(intendedTickLength);
15994
15995     /* if the time remaining has fallen below the alarm threshold, sound the
15996      * alarm. if the alarm has sounded and (due to a takeback or time control
15997      * with increment) the time remaining has increased to a level above the
15998      * threshold, reset the alarm so it can sound again.
15999      */
16000
16001     if (appData.icsActive && appData.icsAlarm) {
16002
16003         /* make sure we are dealing with the user's clock */
16004         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16005                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16006            )) return;
16007
16008         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16009             alarmSounded = FALSE;
16010         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16011             PlayAlarmSound();
16012             alarmSounded = TRUE;
16013         }
16014     }
16015 }
16016
16017
16018 /* A player has just moved, so stop the previously running
16019    clock and (if in clock mode) start the other one.
16020    We redisplay both clocks in case we're in ICS mode, because
16021    ICS gives us an update to both clocks after every move.
16022    Note that this routine is called *after* forwardMostMove
16023    is updated, so the last fractional tick must be subtracted
16024    from the color that is *not* on move now.
16025 */
16026 void
16027 SwitchClocks (int newMoveNr)
16028 {
16029     long lastTickLength;
16030     TimeMark now;
16031     int flagged = FALSE;
16032
16033     GetTimeMark(&now);
16034
16035     if (StopClockTimer() && appData.clockMode) {
16036         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16037         if (!WhiteOnMove(forwardMostMove)) {
16038             if(blackNPS >= 0) lastTickLength = 0;
16039             blackTimeRemaining -= lastTickLength;
16040            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16041 //         if(pvInfoList[forwardMostMove].time == -1)
16042                  pvInfoList[forwardMostMove].time =               // use GUI time
16043                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16044         } else {
16045            if(whiteNPS >= 0) lastTickLength = 0;
16046            whiteTimeRemaining -= lastTickLength;
16047            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16048 //         if(pvInfoList[forwardMostMove].time == -1)
16049                  pvInfoList[forwardMostMove].time =
16050                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16051         }
16052         flagged = CheckFlags();
16053     }
16054     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16055     CheckTimeControl();
16056
16057     if (flagged || !appData.clockMode) return;
16058
16059     switch (gameMode) {
16060       case MachinePlaysBlack:
16061       case MachinePlaysWhite:
16062       case BeginningOfGame:
16063         if (pausing) return;
16064         break;
16065
16066       case EditGame:
16067       case PlayFromGameFile:
16068       case IcsExamining:
16069         return;
16070
16071       default:
16072         break;
16073     }
16074
16075     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16076         if(WhiteOnMove(forwardMostMove))
16077              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16078         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16079     }
16080
16081     tickStartTM = now;
16082     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16083       whiteTimeRemaining : blackTimeRemaining);
16084     StartClockTimer(intendedTickLength);
16085 }
16086
16087
16088 /* Stop both clocks */
16089 void
16090 StopClocks ()
16091 {
16092     long lastTickLength;
16093     TimeMark now;
16094
16095     if (!StopClockTimer()) return;
16096     if (!appData.clockMode) return;
16097
16098     GetTimeMark(&now);
16099
16100     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16101     if (WhiteOnMove(forwardMostMove)) {
16102         if(whiteNPS >= 0) lastTickLength = 0;
16103         whiteTimeRemaining -= lastTickLength;
16104         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16105     } else {
16106         if(blackNPS >= 0) lastTickLength = 0;
16107         blackTimeRemaining -= lastTickLength;
16108         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16109     }
16110     CheckFlags();
16111 }
16112
16113 /* Start clock of player on move.  Time may have been reset, so
16114    if clock is already running, stop and restart it. */
16115 void
16116 StartClocks ()
16117 {
16118     (void) StopClockTimer(); /* in case it was running already */
16119     DisplayBothClocks();
16120     if (CheckFlags()) return;
16121
16122     if (!appData.clockMode) return;
16123     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16124
16125     GetTimeMark(&tickStartTM);
16126     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16127       whiteTimeRemaining : blackTimeRemaining);
16128
16129    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16130     whiteNPS = blackNPS = -1;
16131     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16132        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16133         whiteNPS = first.nps;
16134     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16135        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16136         blackNPS = first.nps;
16137     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16138         whiteNPS = second.nps;
16139     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16140         blackNPS = second.nps;
16141     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16142
16143     StartClockTimer(intendedTickLength);
16144 }
16145
16146 char *
16147 TimeString (long ms)
16148 {
16149     long second, minute, hour, day;
16150     char *sign = "";
16151     static char buf[32];
16152
16153     if (ms > 0 && ms <= 9900) {
16154       /* convert milliseconds to tenths, rounding up */
16155       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16156
16157       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16158       return buf;
16159     }
16160
16161     /* convert milliseconds to seconds, rounding up */
16162     /* use floating point to avoid strangeness of integer division
16163        with negative dividends on many machines */
16164     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16165
16166     if (second < 0) {
16167         sign = "-";
16168         second = -second;
16169     }
16170
16171     day = second / (60 * 60 * 24);
16172     second = second % (60 * 60 * 24);
16173     hour = second / (60 * 60);
16174     second = second % (60 * 60);
16175     minute = second / 60;
16176     second = second % 60;
16177
16178     if (day > 0)
16179       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16180               sign, day, hour, minute, second);
16181     else if (hour > 0)
16182       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16183     else
16184       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16185
16186     return buf;
16187 }
16188
16189
16190 /*
16191  * This is necessary because some C libraries aren't ANSI C compliant yet.
16192  */
16193 char *
16194 StrStr (char *string, char *match)
16195 {
16196     int i, length;
16197
16198     length = strlen(match);
16199
16200     for (i = strlen(string) - length; i >= 0; i--, string++)
16201       if (!strncmp(match, string, length))
16202         return string;
16203
16204     return NULL;
16205 }
16206
16207 char *
16208 StrCaseStr (char *string, char *match)
16209 {
16210     int i, j, length;
16211
16212     length = strlen(match);
16213
16214     for (i = strlen(string) - length; i >= 0; i--, string++) {
16215         for (j = 0; j < length; j++) {
16216             if (ToLower(match[j]) != ToLower(string[j]))
16217               break;
16218         }
16219         if (j == length) return string;
16220     }
16221
16222     return NULL;
16223 }
16224
16225 #ifndef _amigados
16226 int
16227 StrCaseCmp (char *s1, char *s2)
16228 {
16229     char c1, c2;
16230
16231     for (;;) {
16232         c1 = ToLower(*s1++);
16233         c2 = ToLower(*s2++);
16234         if (c1 > c2) return 1;
16235         if (c1 < c2) return -1;
16236         if (c1 == NULLCHAR) return 0;
16237     }
16238 }
16239
16240
16241 int
16242 ToLower (int c)
16243 {
16244     return isupper(c) ? tolower(c) : c;
16245 }
16246
16247
16248 int
16249 ToUpper (int c)
16250 {
16251     return islower(c) ? toupper(c) : c;
16252 }
16253 #endif /* !_amigados    */
16254
16255 char *
16256 StrSave (char *s)
16257 {
16258   char *ret;
16259
16260   if ((ret = (char *) malloc(strlen(s) + 1)))
16261     {
16262       safeStrCpy(ret, s, strlen(s)+1);
16263     }
16264   return ret;
16265 }
16266
16267 char *
16268 StrSavePtr (char *s, char **savePtr)
16269 {
16270     if (*savePtr) {
16271         free(*savePtr);
16272     }
16273     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16274       safeStrCpy(*savePtr, s, strlen(s)+1);
16275     }
16276     return(*savePtr);
16277 }
16278
16279 char *
16280 PGNDate ()
16281 {
16282     time_t clock;
16283     struct tm *tm;
16284     char buf[MSG_SIZ];
16285
16286     clock = time((time_t *)NULL);
16287     tm = localtime(&clock);
16288     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16289             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16290     return StrSave(buf);
16291 }
16292
16293
16294 char *
16295 PositionToFEN (int move, char *overrideCastling)
16296 {
16297     int i, j, fromX, fromY, toX, toY;
16298     int whiteToPlay;
16299     char buf[MSG_SIZ];
16300     char *p, *q;
16301     int emptycount;
16302     ChessSquare piece;
16303
16304     whiteToPlay = (gameMode == EditPosition) ?
16305       !blackPlaysFirst : (move % 2 == 0);
16306     p = buf;
16307
16308     /* Piece placement data */
16309     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16310         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16311         emptycount = 0;
16312         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16313             if (boards[move][i][j] == EmptySquare) {
16314                 emptycount++;
16315             } else { ChessSquare piece = boards[move][i][j];
16316                 if (emptycount > 0) {
16317                     if(emptycount<10) /* [HGM] can be >= 10 */
16318                         *p++ = '0' + emptycount;
16319                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16320                     emptycount = 0;
16321                 }
16322                 if(PieceToChar(piece) == '+') {
16323                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16324                     *p++ = '+';
16325                     piece = (ChessSquare)(DEMOTED piece);
16326                 }
16327                 *p++ = PieceToChar(piece);
16328                 if(p[-1] == '~') {
16329                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16330                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16331                     *p++ = '~';
16332                 }
16333             }
16334         }
16335         if (emptycount > 0) {
16336             if(emptycount<10) /* [HGM] can be >= 10 */
16337                 *p++ = '0' + emptycount;
16338             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16339             emptycount = 0;
16340         }
16341         *p++ = '/';
16342     }
16343     *(p - 1) = ' ';
16344
16345     /* [HGM] print Crazyhouse or Shogi holdings */
16346     if( gameInfo.holdingsWidth ) {
16347         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16348         q = p;
16349         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16350             piece = boards[move][i][BOARD_WIDTH-1];
16351             if( piece != EmptySquare )
16352               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16353                   *p++ = PieceToChar(piece);
16354         }
16355         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16356             piece = boards[move][BOARD_HEIGHT-i-1][0];
16357             if( piece != EmptySquare )
16358               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16359                   *p++ = PieceToChar(piece);
16360         }
16361
16362         if( q == p ) *p++ = '-';
16363         *p++ = ']';
16364         *p++ = ' ';
16365     }
16366
16367     /* Active color */
16368     *p++ = whiteToPlay ? 'w' : 'b';
16369     *p++ = ' ';
16370
16371   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16372     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16373   } else {
16374   if(nrCastlingRights) {
16375      q = p;
16376      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16377        /* [HGM] write directly from rights */
16378            if(boards[move][CASTLING][2] != NoRights &&
16379               boards[move][CASTLING][0] != NoRights   )
16380                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16381            if(boards[move][CASTLING][2] != NoRights &&
16382               boards[move][CASTLING][1] != NoRights   )
16383                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16384            if(boards[move][CASTLING][5] != NoRights &&
16385               boards[move][CASTLING][3] != NoRights   )
16386                 *p++ = boards[move][CASTLING][3] + AAA;
16387            if(boards[move][CASTLING][5] != NoRights &&
16388               boards[move][CASTLING][4] != NoRights   )
16389                 *p++ = boards[move][CASTLING][4] + AAA;
16390      } else {
16391
16392         /* [HGM] write true castling rights */
16393         if( nrCastlingRights == 6 ) {
16394             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16395                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16396             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16397                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16398             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16399                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16400             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16401                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16402         }
16403      }
16404      if (q == p) *p++ = '-'; /* No castling rights */
16405      *p++ = ' ';
16406   }
16407
16408   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16409      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16410     /* En passant target square */
16411     if (move > backwardMostMove) {
16412         fromX = moveList[move - 1][0] - AAA;
16413         fromY = moveList[move - 1][1] - ONE;
16414         toX = moveList[move - 1][2] - AAA;
16415         toY = moveList[move - 1][3] - ONE;
16416         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16417             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16418             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16419             fromX == toX) {
16420             /* 2-square pawn move just happened */
16421             *p++ = toX + AAA;
16422             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16423         } else {
16424             *p++ = '-';
16425         }
16426     } else if(move == backwardMostMove) {
16427         // [HGM] perhaps we should always do it like this, and forget the above?
16428         if((signed char)boards[move][EP_STATUS] >= 0) {
16429             *p++ = boards[move][EP_STATUS] + AAA;
16430             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16431         } else {
16432             *p++ = '-';
16433         }
16434     } else {
16435         *p++ = '-';
16436     }
16437     *p++ = ' ';
16438   }
16439   }
16440
16441     /* [HGM] find reversible plies */
16442     {   int i = 0, j=move;
16443
16444         if (appData.debugMode) { int k;
16445             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16446             for(k=backwardMostMove; k<=forwardMostMove; k++)
16447                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16448
16449         }
16450
16451         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16452         if( j == backwardMostMove ) i += initialRulePlies;
16453         sprintf(p, "%d ", i);
16454         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16455     }
16456     /* Fullmove number */
16457     sprintf(p, "%d", (move / 2) + 1);
16458
16459     return StrSave(buf);
16460 }
16461
16462 Boolean
16463 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16464 {
16465     int i, j;
16466     char *p, c;
16467     int emptycount;
16468     ChessSquare piece;
16469
16470     p = fen;
16471
16472     /* [HGM] by default clear Crazyhouse holdings, if present */
16473     if(gameInfo.holdingsWidth) {
16474        for(i=0; i<BOARD_HEIGHT; i++) {
16475            board[i][0]             = EmptySquare; /* black holdings */
16476            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16477            board[i][1]             = (ChessSquare) 0; /* black counts */
16478            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16479        }
16480     }
16481
16482     /* Piece placement data */
16483     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16484         j = 0;
16485         for (;;) {
16486             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16487                 if (*p == '/') p++;
16488                 emptycount = gameInfo.boardWidth - j;
16489                 while (emptycount--)
16490                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16491                 break;
16492 #if(BOARD_FILES >= 10)
16493             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16494                 p++; emptycount=10;
16495                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16496                 while (emptycount--)
16497                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16498 #endif
16499             } else if (isdigit(*p)) {
16500                 emptycount = *p++ - '0';
16501                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16502                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16503                 while (emptycount--)
16504                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16505             } else if (*p == '+' || isalpha(*p)) {
16506                 if (j >= gameInfo.boardWidth) return FALSE;
16507                 if(*p=='+') {
16508                     piece = CharToPiece(*++p);
16509                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16510                     piece = (ChessSquare) (PROMOTED piece ); p++;
16511                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16512                 } else piece = CharToPiece(*p++);
16513
16514                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16515                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16516                     piece = (ChessSquare) (PROMOTED piece);
16517                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16518                     p++;
16519                 }
16520                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16521             } else {
16522                 return FALSE;
16523             }
16524         }
16525     }
16526     while (*p == '/' || *p == ' ') p++;
16527
16528     /* [HGM] look for Crazyhouse holdings here */
16529     while(*p==' ') p++;
16530     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16531         if(*p == '[') p++;
16532         if(*p == '-' ) p++; /* empty holdings */ else {
16533             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16534             /* if we would allow FEN reading to set board size, we would   */
16535             /* have to add holdings and shift the board read so far here   */
16536             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16537                 p++;
16538                 if((int) piece >= (int) BlackPawn ) {
16539                     i = (int)piece - (int)BlackPawn;
16540                     i = PieceToNumber((ChessSquare)i);
16541                     if( i >= gameInfo.holdingsSize ) return FALSE;
16542                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16543                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16544                 } else {
16545                     i = (int)piece - (int)WhitePawn;
16546                     i = PieceToNumber((ChessSquare)i);
16547                     if( i >= gameInfo.holdingsSize ) return FALSE;
16548                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16549                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16550                 }
16551             }
16552         }
16553         if(*p == ']') p++;
16554     }
16555
16556     while(*p == ' ') p++;
16557
16558     /* Active color */
16559     c = *p++;
16560     if(appData.colorNickNames) {
16561       if( c == appData.colorNickNames[0] ) c = 'w'; else
16562       if( c == appData.colorNickNames[1] ) c = 'b';
16563     }
16564     switch (c) {
16565       case 'w':
16566         *blackPlaysFirst = FALSE;
16567         break;
16568       case 'b':
16569         *blackPlaysFirst = TRUE;
16570         break;
16571       default:
16572         return FALSE;
16573     }
16574
16575     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16576     /* return the extra info in global variiables             */
16577
16578     /* set defaults in case FEN is incomplete */
16579     board[EP_STATUS] = EP_UNKNOWN;
16580     for(i=0; i<nrCastlingRights; i++ ) {
16581         board[CASTLING][i] =
16582             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16583     }   /* assume possible unless obviously impossible */
16584     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16585     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16586     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16587                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16588     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16589     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16590     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16591                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16592     FENrulePlies = 0;
16593
16594     while(*p==' ') p++;
16595     if(nrCastlingRights) {
16596       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16597           /* castling indicator present, so default becomes no castlings */
16598           for(i=0; i<nrCastlingRights; i++ ) {
16599                  board[CASTLING][i] = NoRights;
16600           }
16601       }
16602       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16603              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16604              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16605              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16606         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16607
16608         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16609             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16610             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16611         }
16612         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16613             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16614         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16615                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16616         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16617                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16618         switch(c) {
16619           case'K':
16620               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16621               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16622               board[CASTLING][2] = whiteKingFile;
16623               break;
16624           case'Q':
16625               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16626               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16627               board[CASTLING][2] = whiteKingFile;
16628               break;
16629           case'k':
16630               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16631               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16632               board[CASTLING][5] = blackKingFile;
16633               break;
16634           case'q':
16635               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16636               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16637               board[CASTLING][5] = blackKingFile;
16638           case '-':
16639               break;
16640           default: /* FRC castlings */
16641               if(c >= 'a') { /* black rights */
16642                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16643                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16644                   if(i == BOARD_RGHT) break;
16645                   board[CASTLING][5] = i;
16646                   c -= AAA;
16647                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16648                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16649                   if(c > i)
16650                       board[CASTLING][3] = c;
16651                   else
16652                       board[CASTLING][4] = c;
16653               } else { /* white rights */
16654                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16655                     if(board[0][i] == WhiteKing) break;
16656                   if(i == BOARD_RGHT) break;
16657                   board[CASTLING][2] = i;
16658                   c -= AAA - 'a' + 'A';
16659                   if(board[0][c] >= WhiteKing) break;
16660                   if(c > i)
16661                       board[CASTLING][0] = c;
16662                   else
16663                       board[CASTLING][1] = c;
16664               }
16665         }
16666       }
16667       for(i=0; i<nrCastlingRights; i++)
16668         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16669     if (appData.debugMode) {
16670         fprintf(debugFP, "FEN castling rights:");
16671         for(i=0; i<nrCastlingRights; i++)
16672         fprintf(debugFP, " %d", board[CASTLING][i]);
16673         fprintf(debugFP, "\n");
16674     }
16675
16676       while(*p==' ') p++;
16677     }
16678
16679     /* read e.p. field in games that know e.p. capture */
16680     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16681        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16682       if(*p=='-') {
16683         p++; board[EP_STATUS] = EP_NONE;
16684       } else {
16685          char c = *p++ - AAA;
16686
16687          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16688          if(*p >= '0' && *p <='9') p++;
16689          board[EP_STATUS] = c;
16690       }
16691     }
16692
16693
16694     if(sscanf(p, "%d", &i) == 1) {
16695         FENrulePlies = i; /* 50-move ply counter */
16696         /* (The move number is still ignored)    */
16697     }
16698
16699     return TRUE;
16700 }
16701
16702 void
16703 EditPositionPasteFEN (char *fen)
16704 {
16705   if (fen != NULL) {
16706     Board initial_position;
16707
16708     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16709       DisplayError(_("Bad FEN position in clipboard"), 0);
16710       return ;
16711     } else {
16712       int savedBlackPlaysFirst = blackPlaysFirst;
16713       EditPositionEvent();
16714       blackPlaysFirst = savedBlackPlaysFirst;
16715       CopyBoard(boards[0], initial_position);
16716       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16717       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16718       DisplayBothClocks();
16719       DrawPosition(FALSE, boards[currentMove]);
16720     }
16721   }
16722 }
16723
16724 static char cseq[12] = "\\   ";
16725
16726 Boolean
16727 set_cont_sequence (char *new_seq)
16728 {
16729     int len;
16730     Boolean ret;
16731
16732     // handle bad attempts to set the sequence
16733         if (!new_seq)
16734                 return 0; // acceptable error - no debug
16735
16736     len = strlen(new_seq);
16737     ret = (len > 0) && (len < sizeof(cseq));
16738     if (ret)
16739       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16740     else if (appData.debugMode)
16741       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16742     return ret;
16743 }
16744
16745 /*
16746     reformat a source message so words don't cross the width boundary.  internal
16747     newlines are not removed.  returns the wrapped size (no null character unless
16748     included in source message).  If dest is NULL, only calculate the size required
16749     for the dest buffer.  lp argument indicats line position upon entry, and it's
16750     passed back upon exit.
16751 */
16752 int
16753 wrap (char *dest, char *src, int count, int width, int *lp)
16754 {
16755     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16756
16757     cseq_len = strlen(cseq);
16758     old_line = line = *lp;
16759     ansi = len = clen = 0;
16760
16761     for (i=0; i < count; i++)
16762     {
16763         if (src[i] == '\033')
16764             ansi = 1;
16765
16766         // if we hit the width, back up
16767         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16768         {
16769             // store i & len in case the word is too long
16770             old_i = i, old_len = len;
16771
16772             // find the end of the last word
16773             while (i && src[i] != ' ' && src[i] != '\n')
16774             {
16775                 i--;
16776                 len--;
16777             }
16778
16779             // word too long?  restore i & len before splitting it
16780             if ((old_i-i+clen) >= width)
16781             {
16782                 i = old_i;
16783                 len = old_len;
16784             }
16785
16786             // extra space?
16787             if (i && src[i-1] == ' ')
16788                 len--;
16789
16790             if (src[i] != ' ' && src[i] != '\n')
16791             {
16792                 i--;
16793                 if (len)
16794                     len--;
16795             }
16796
16797             // now append the newline and continuation sequence
16798             if (dest)
16799                 dest[len] = '\n';
16800             len++;
16801             if (dest)
16802                 strncpy(dest+len, cseq, cseq_len);
16803             len += cseq_len;
16804             line = cseq_len;
16805             clen = cseq_len;
16806             continue;
16807         }
16808
16809         if (dest)
16810             dest[len] = src[i];
16811         len++;
16812         if (!ansi)
16813             line++;
16814         if (src[i] == '\n')
16815             line = 0;
16816         if (src[i] == 'm')
16817             ansi = 0;
16818     }
16819     if (dest && appData.debugMode)
16820     {
16821         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16822             count, width, line, len, *lp);
16823         show_bytes(debugFP, src, count);
16824         fprintf(debugFP, "\ndest: ");
16825         show_bytes(debugFP, dest, len);
16826         fprintf(debugFP, "\n");
16827     }
16828     *lp = dest ? line : old_line;
16829
16830     return len;
16831 }
16832
16833 // [HGM] vari: routines for shelving variations
16834 Boolean modeRestore = FALSE;
16835
16836 void
16837 PushInner (int firstMove, int lastMove)
16838 {
16839         int i, j, nrMoves = lastMove - firstMove;
16840
16841         // push current tail of game on stack
16842         savedResult[storedGames] = gameInfo.result;
16843         savedDetails[storedGames] = gameInfo.resultDetails;
16844         gameInfo.resultDetails = NULL;
16845         savedFirst[storedGames] = firstMove;
16846         savedLast [storedGames] = lastMove;
16847         savedFramePtr[storedGames] = framePtr;
16848         framePtr -= nrMoves; // reserve space for the boards
16849         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16850             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16851             for(j=0; j<MOVE_LEN; j++)
16852                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16853             for(j=0; j<2*MOVE_LEN; j++)
16854                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16855             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16856             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16857             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16858             pvInfoList[firstMove+i-1].depth = 0;
16859             commentList[framePtr+i] = commentList[firstMove+i];
16860             commentList[firstMove+i] = NULL;
16861         }
16862
16863         storedGames++;
16864         forwardMostMove = firstMove; // truncate game so we can start variation
16865 }
16866
16867 void
16868 PushTail (int firstMove, int lastMove)
16869 {
16870         if(appData.icsActive) { // only in local mode
16871                 forwardMostMove = currentMove; // mimic old ICS behavior
16872                 return;
16873         }
16874         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16875
16876         PushInner(firstMove, lastMove);
16877         if(storedGames == 1) GreyRevert(FALSE);
16878         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16879 }
16880
16881 void
16882 PopInner (Boolean annotate)
16883 {
16884         int i, j, nrMoves;
16885         char buf[8000], moveBuf[20];
16886
16887         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16888         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16889         nrMoves = savedLast[storedGames] - currentMove;
16890         if(annotate) {
16891                 int cnt = 10;
16892                 if(!WhiteOnMove(currentMove))
16893                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16894                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16895                 for(i=currentMove; i<forwardMostMove; i++) {
16896                         if(WhiteOnMove(i))
16897                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16898                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16899                         strcat(buf, moveBuf);
16900                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16901                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16902                 }
16903                 strcat(buf, ")");
16904         }
16905         for(i=1; i<=nrMoves; i++) { // copy last variation back
16906             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16907             for(j=0; j<MOVE_LEN; j++)
16908                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16909             for(j=0; j<2*MOVE_LEN; j++)
16910                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16911             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16912             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16913             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16914             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16915             commentList[currentMove+i] = commentList[framePtr+i];
16916             commentList[framePtr+i] = NULL;
16917         }
16918         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16919         framePtr = savedFramePtr[storedGames];
16920         gameInfo.result = savedResult[storedGames];
16921         if(gameInfo.resultDetails != NULL) {
16922             free(gameInfo.resultDetails);
16923       }
16924         gameInfo.resultDetails = savedDetails[storedGames];
16925         forwardMostMove = currentMove + nrMoves;
16926 }
16927
16928 Boolean
16929 PopTail (Boolean annotate)
16930 {
16931         if(appData.icsActive) return FALSE; // only in local mode
16932         if(!storedGames) return FALSE; // sanity
16933         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16934
16935         PopInner(annotate);
16936         if(currentMove < forwardMostMove) ForwardEvent(); else
16937         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16938
16939         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16940         return TRUE;
16941 }
16942
16943 void
16944 CleanupTail ()
16945 {       // remove all shelved variations
16946         int i;
16947         for(i=0; i<storedGames; i++) {
16948             if(savedDetails[i])
16949                 free(savedDetails[i]);
16950             savedDetails[i] = NULL;
16951         }
16952         for(i=framePtr; i<MAX_MOVES; i++) {
16953                 if(commentList[i]) free(commentList[i]);
16954                 commentList[i] = NULL;
16955         }
16956         framePtr = MAX_MOVES-1;
16957         storedGames = 0;
16958 }
16959
16960 void
16961 LoadVariation (int index, char *text)
16962 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16963         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16964         int level = 0, move;
16965
16966         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16967         // first find outermost bracketing variation
16968         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16969             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16970                 if(*p == '{') wait = '}'; else
16971                 if(*p == '[') wait = ']'; else
16972                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16973                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16974             }
16975             if(*p == wait) wait = NULLCHAR; // closing ]} found
16976             p++;
16977         }
16978         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16979         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16980         end[1] = NULLCHAR; // clip off comment beyond variation
16981         ToNrEvent(currentMove-1);
16982         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16983         // kludge: use ParsePV() to append variation to game
16984         move = currentMove;
16985         ParsePV(start, TRUE, TRUE);
16986         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16987         ClearPremoveHighlights();
16988         CommentPopDown();
16989         ToNrEvent(currentMove+1);
16990 }
16991