Put recently used engines in WB menu
[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
397 /*
398     [AS] Note: sometimes, the sscanf() function is used to parse the input
399     into a fixed-size buffer. Because of this, we must be prepared to
400     receive strings as long as the size of the input buffer, which is currently
401     set to 4K for Windows and 8K for the rest.
402     So, we must either allocate sufficiently large buffers here, or
403     reduce the size of the input buffer in the input reading part.
404 */
405
406 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
407 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
408 char thinkOutput1[MSG_SIZ*10];
409
410 ChessProgramState first, second, pairing;
411
412 /* premove variables */
413 int premoveToX = 0;
414 int premoveToY = 0;
415 int premoveFromX = 0;
416 int premoveFromY = 0;
417 int premovePromoChar = 0;
418 int gotPremove = 0;
419 Boolean alarmSounded;
420 /* end premove variables */
421
422 char *ics_prefix = "$";
423 int ics_type = ICS_GENERIC;
424
425 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
426 int pauseExamForwardMostMove = 0;
427 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
428 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
429 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
430 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
431 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
432 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
433 int whiteFlag = FALSE, blackFlag = FALSE;
434 int userOfferedDraw = FALSE;
435 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
436 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
437 int cmailMoveType[CMAIL_MAX_GAMES];
438 long ics_clock_paused = 0;
439 ProcRef icsPR = NoProc, cmailPR = NoProc;
440 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
441 GameMode gameMode = BeginningOfGame;
442 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
443 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
444 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
445 int hiddenThinkOutputState = 0; /* [AS] */
446 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
447 int adjudicateLossPlies = 6;
448 char white_holding[64], black_holding[64];
449 TimeMark lastNodeCountTime;
450 long lastNodeCount=0;
451 int shiftKey; // [HGM] set by mouse handler
452
453 int have_sent_ICS_logon = 0;
454 int movesPerSession;
455 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
456 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
457 Boolean adjustedClock;
458 long timeControl_2; /* [AS] Allow separate time controls */
459 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
460 long timeRemaining[2][MAX_MOVES];
461 int matchGame = 0, nextGame = 0, roundNr = 0;
462 Boolean waitingForGame = FALSE;
463 TimeMark programStartTime, pauseStart;
464 char ics_handle[MSG_SIZ];
465 int have_set_title = 0;
466
467 /* animateTraining preserves the state of appData.animate
468  * when Training mode is activated. This allows the
469  * response to be animated when appData.animate == TRUE and
470  * appData.animateDragging == TRUE.
471  */
472 Boolean animateTraining;
473
474 GameInfo gameInfo;
475
476 AppData appData;
477
478 Board boards[MAX_MOVES];
479 /* [HGM] Following 7 needed for accurate legality tests: */
480 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
481 signed char  initialRights[BOARD_FILES];
482 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
483 int   initialRulePlies, FENrulePlies;
484 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
485 int loadFlag = 0;
486 Boolean shuffleOpenings;
487 int mute; // mute all sounds
488
489 // [HGM] vari: next 12 to save and restore variations
490 #define MAX_VARIATIONS 10
491 int framePtr = MAX_MOVES-1; // points to free stack entry
492 int storedGames = 0;
493 int savedFirst[MAX_VARIATIONS];
494 int savedLast[MAX_VARIATIONS];
495 int savedFramePtr[MAX_VARIATIONS];
496 char *savedDetails[MAX_VARIATIONS];
497 ChessMove savedResult[MAX_VARIATIONS];
498
499 void PushTail P((int firstMove, int lastMove));
500 Boolean PopTail P((Boolean annotate));
501 void PushInner P((int firstMove, int lastMove));
502 void PopInner P((Boolean annotate));
503 void CleanupTail P((void));
504
505 ChessSquare  FIDEArray[2][BOARD_FILES] = {
506     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
507         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
508     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
509         BlackKing, BlackBishop, BlackKnight, BlackRook }
510 };
511
512 ChessSquare twoKingsArray[2][BOARD_FILES] = {
513     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
514         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
515     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
516         BlackKing, BlackKing, BlackKnight, BlackRook }
517 };
518
519 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
520     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
521         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
522     { BlackRook, BlackMan, BlackBishop, BlackQueen,
523         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
524 };
525
526 ChessSquare SpartanArray[2][BOARD_FILES] = {
527     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
528         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
529     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
530         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
531 };
532
533 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
534     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
535         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
536     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
537         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
538 };
539
540 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
541     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
542         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
544         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
545 };
546
547 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
548     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
549         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
550     { BlackRook, BlackKnight, BlackMan, BlackFerz,
551         BlackKing, BlackMan, BlackKnight, BlackRook }
552 };
553
554
555 #if (BOARD_FILES>=10)
556 ChessSquare ShogiArray[2][BOARD_FILES] = {
557     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
558         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
559     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
560         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
561 };
562
563 ChessSquare XiangqiArray[2][BOARD_FILES] = {
564     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
565         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
566     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
567         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
568 };
569
570 ChessSquare CapablancaArray[2][BOARD_FILES] = {
571     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
572         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
573     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
574         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
575 };
576
577 ChessSquare GreatArray[2][BOARD_FILES] = {
578     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
579         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
580     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
581         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
582 };
583
584 ChessSquare JanusArray[2][BOARD_FILES] = {
585     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
586         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
587     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
588         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
589 };
590
591 ChessSquare GrandArray[2][BOARD_FILES] = {
592     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
593         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
594     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
595         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
596 };
597
598 #ifdef GOTHIC
599 ChessSquare GothicArray[2][BOARD_FILES] = {
600     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
601         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
602     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
603         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
604 };
605 #else // !GOTHIC
606 #define GothicArray CapablancaArray
607 #endif // !GOTHIC
608
609 #ifdef FALCON
610 ChessSquare FalconArray[2][BOARD_FILES] = {
611     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
612         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
613     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
614         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
615 };
616 #else // !FALCON
617 #define FalconArray CapablancaArray
618 #endif // !FALCON
619
620 #else // !(BOARD_FILES>=10)
621 #define XiangqiPosition FIDEArray
622 #define CapablancaArray FIDEArray
623 #define GothicArray FIDEArray
624 #define GreatArray FIDEArray
625 #endif // !(BOARD_FILES>=10)
626
627 #if (BOARD_FILES>=12)
628 ChessSquare CourierArray[2][BOARD_FILES] = {
629     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
630         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
631     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
632         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
633 };
634 #else // !(BOARD_FILES>=12)
635 #define CourierArray CapablancaArray
636 #endif // !(BOARD_FILES>=12)
637
638
639 Board initialPosition;
640
641
642 /* Convert str to a rating. Checks for special cases of "----",
643
644    "++++", etc. Also strips ()'s */
645 int
646 string_to_rating (char *str)
647 {
648   while(*str && !isdigit(*str)) ++str;
649   if (!*str)
650     return 0;   /* One of the special "no rating" cases */
651   else
652     return atoi(str);
653 }
654
655 void
656 ClearProgramStats ()
657 {
658     /* Init programStats */
659     programStats.movelist[0] = 0;
660     programStats.depth = 0;
661     programStats.nr_moves = 0;
662     programStats.moves_left = 0;
663     programStats.nodes = 0;
664     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
665     programStats.score = 0;
666     programStats.got_only_move = 0;
667     programStats.got_fail = 0;
668     programStats.line_is_book = 0;
669 }
670
671 void
672 CommonEngineInit ()
673 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
674     if (appData.firstPlaysBlack) {
675         first.twoMachinesColor = "black\n";
676         second.twoMachinesColor = "white\n";
677     } else {
678         first.twoMachinesColor = "white\n";
679         second.twoMachinesColor = "black\n";
680     }
681
682     first.other = &second;
683     second.other = &first;
684
685     { float norm = 1;
686         if(appData.timeOddsMode) {
687             norm = appData.timeOdds[0];
688             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
689         }
690         first.timeOdds  = appData.timeOdds[0]/norm;
691         second.timeOdds = appData.timeOdds[1]/norm;
692     }
693
694     if(programVersion) free(programVersion);
695     if (appData.noChessProgram) {
696         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
697         sprintf(programVersion, "%s", PACKAGE_STRING);
698     } else {
699       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
700       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
701       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
702     }
703 }
704
705 void
706 UnloadEngine (ChessProgramState *cps)
707 {
708         /* Kill off first chess program */
709         if (cps->isr != NULL)
710           RemoveInputSource(cps->isr);
711         cps->isr = NULL;
712
713         if (cps->pr != NoProc) {
714             ExitAnalyzeMode();
715             DoSleep( appData.delayBeforeQuit );
716             SendToProgram("quit\n", cps);
717             DoSleep( appData.delayAfterQuit );
718             DestroyChildProcess(cps->pr, cps->useSigterm);
719         }
720         cps->pr = NoProc;
721         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
722 }
723
724 void
725 ClearOptions (ChessProgramState *cps)
726 {
727     int i;
728     cps->nrOptions = cps->comboCnt = 0;
729     for(i=0; i<MAX_OPTIONS; i++) {
730         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
731         cps->option[i].textValue = 0;
732     }
733 }
734
735 char *engineNames[] = {
736 "first",
737 "second"
738 };
739
740 void
741 InitEngine (ChessProgramState *cps, int n)
742 {   // [HGM] all engine initialiation put in a function that does one engine
743
744     ClearOptions(cps);
745
746     cps->which = engineNames[n];
747     cps->maybeThinking = FALSE;
748     cps->pr = NoProc;
749     cps->isr = NULL;
750     cps->sendTime = 2;
751     cps->sendDrawOffers = 1;
752
753     cps->program = appData.chessProgram[n];
754     cps->host = appData.host[n];
755     cps->dir = appData.directory[n];
756     cps->initString = appData.engInitString[n];
757     cps->computerString = appData.computerString[n];
758     cps->useSigint  = TRUE;
759     cps->useSigterm = TRUE;
760     cps->reuse = appData.reuse[n];
761     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
762     cps->useSetboard = FALSE;
763     cps->useSAN = FALSE;
764     cps->usePing = FALSE;
765     cps->lastPing = 0;
766     cps->lastPong = 0;
767     cps->usePlayother = FALSE;
768     cps->useColors = TRUE;
769     cps->useUsermove = FALSE;
770     cps->sendICS = FALSE;
771     cps->sendName = appData.icsActive;
772     cps->sdKludge = FALSE;
773     cps->stKludge = FALSE;
774     TidyProgramName(cps->program, cps->host, cps->tidy);
775     cps->matchWins = 0;
776     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
777     cps->analysisSupport = 2; /* detect */
778     cps->analyzing = FALSE;
779     cps->initDone = FALSE;
780
781     /* New features added by Tord: */
782     cps->useFEN960 = FALSE;
783     cps->useOOCastle = TRUE;
784     /* End of new features added by Tord. */
785     cps->fenOverride  = appData.fenOverride[n];
786
787     /* [HGM] time odds: set factor for each machine */
788     cps->timeOdds  = appData.timeOdds[n];
789
790     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
791     cps->accumulateTC = appData.accumulateTC[n];
792     cps->maxNrOfSessions = 1;
793
794     /* [HGM] debug */
795     cps->debug = FALSE;
796
797     cps->supportsNPS = UNKNOWN;
798     cps->memSize = FALSE;
799     cps->maxCores = FALSE;
800     cps->egtFormats[0] = NULLCHAR;
801
802     /* [HGM] options */
803     cps->optionSettings  = appData.engOptions[n];
804
805     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
806     cps->isUCI = appData.isUCI[n]; /* [AS] */
807     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
808
809     if (appData.protocolVersion[n] > PROTOVER
810         || appData.protocolVersion[n] < 1)
811       {
812         char buf[MSG_SIZ];
813         int len;
814
815         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
816                        appData.protocolVersion[n]);
817         if( (len >= MSG_SIZ) && appData.debugMode )
818           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
819
820         DisplayFatalError(buf, 0, 2);
821       }
822     else
823       {
824         cps->protocolVersion = appData.protocolVersion[n];
825       }
826
827     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
828     ParseFeatures(appData.featureDefaults, cps);
829 }
830
831 ChessProgramState *savCps;
832
833 void
834 LoadEngine ()
835 {
836     int i;
837     if(WaitForEngine(savCps, LoadEngine)) return;
838     CommonEngineInit(); // recalculate time odds
839     if(gameInfo.variant != StringToVariant(appData.variant)) {
840         // we changed variant when loading the engine; this forces us to reset
841         Reset(TRUE, savCps != &first);
842         EditGameEvent(); // for consistency with other path, as Reset changes mode
843     }
844     InitChessProgram(savCps, FALSE);
845     SendToProgram("force\n", savCps);
846     DisplayMessage("", "");
847     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
848     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
849     ThawUI();
850     SetGNUMode();
851 }
852
853 void
854 ReplaceEngine (ChessProgramState *cps, int n)
855 {
856     EditGameEvent();
857     UnloadEngine(cps);
858     appData.noChessProgram = FALSE;
859     appData.clockMode = TRUE;
860     InitEngine(cps, n);
861     UpdateLogos(TRUE);
862     if(n) return; // only startup first engine immediately; second can wait
863     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
864     LoadEngine();
865 }
866
867 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
868 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
869
870 static char resetOptions[] = 
871         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
872         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
873         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
874
875 void
876 FloatToFront(char **list, char *engineLine)
877 {
878     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
879     int i=0;
880     if(appData.recentEngines <= 0) return;
881     TidyProgramName(engineLine, "localhost", tidy+1);
882     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
883     strncpy(buf+1, *list, MSG_SIZ-50);
884     if(p = strstr(buf, tidy)) { // tidy name appears in list
885         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
886         while(*p++ = *++q); // squeeze out
887     }
888     strcat(tidy, buf+1); // put list behind tidy name
889     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
890     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
891     ASSIGN(*list, tidy+1);
892 }
893
894 void
895 Load (ChessProgramState *cps, int i)
896 {
897     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
898     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
899         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
900         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
901         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
902         appData.firstProtocolVersion = PROTOVER;
903         ParseArgsFromString(buf);
904         SwapEngines(i);
905         ReplaceEngine(cps, i);
906         FloatToFront(&appData.recentEngineList, engineLine);
907         return;
908     }
909     p = engineName;
910     while(q = strchr(p, SLASH)) p = q+1;
911     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
912     if(engineDir[0] != NULLCHAR)
913         appData.directory[i] = engineDir;
914     else if(p != engineName) { // derive directory from engine path, when not given
915         p[-1] = 0;
916         appData.directory[i] = strdup(engineName);
917         p[-1] = SLASH;
918     } else appData.directory[i] = ".";
919     if(params[0]) {
920         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
921         snprintf(command, MSG_SIZ, "%s %s", p, params);
922         p = command;
923     }
924     appData.chessProgram[i] = strdup(p);
925     appData.isUCI[i] = isUCI;
926     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
927     appData.hasOwnBookUCI[i] = hasBook;
928     if(!nickName[0]) useNick = FALSE;
929     if(useNick) ASSIGN(appData.pgnName[i], nickName);
930     if(addToList) {
931         int len;
932         char quote;
933         q = firstChessProgramNames;
934         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
935         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
936         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
937                         quote, p, quote, appData.directory[i], 
938                         useNick ? " -fn \"" : "",
939                         useNick ? nickName : "",
940                         useNick ? "\"" : "",
941                         v1 ? " -firstProtocolVersion 1" : "",
942                         hasBook ? "" : " -fNoOwnBookUCI",
943                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
944                         storeVariant ? " -variant " : "",
945                         storeVariant ? VariantName(gameInfo.variant) : "");
946         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
947         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
948         if(q)   free(q);
949         FloatToFront(&appData.recentEngineList, buf);
950     }
951     ReplaceEngine(cps, i);
952 }
953
954 void
955 InitTimeControls ()
956 {
957     int matched, min, sec;
958     /*
959      * Parse timeControl resource
960      */
961     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
962                           appData.movesPerSession)) {
963         char buf[MSG_SIZ];
964         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
965         DisplayFatalError(buf, 0, 2);
966     }
967
968     /*
969      * Parse searchTime resource
970      */
971     if (*appData.searchTime != NULLCHAR) {
972         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
973         if (matched == 1) {
974             searchTime = min * 60;
975         } else if (matched == 2) {
976             searchTime = min * 60 + sec;
977         } else {
978             char buf[MSG_SIZ];
979             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
980             DisplayFatalError(buf, 0, 2);
981         }
982     }
983 }
984
985 void
986 InitBackEnd1 ()
987 {
988
989     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
990     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
991
992     GetTimeMark(&programStartTime);
993     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
994     appData.seedBase = random() + (random()<<15);
995     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
996
997     ClearProgramStats();
998     programStats.ok_to_send = 1;
999     programStats.seen_stat = 0;
1000
1001     /*
1002      * Initialize game list
1003      */
1004     ListNew(&gameList);
1005
1006
1007     /*
1008      * Internet chess server status
1009      */
1010     if (appData.icsActive) {
1011         appData.matchMode = FALSE;
1012         appData.matchGames = 0;
1013 #if ZIPPY
1014         appData.noChessProgram = !appData.zippyPlay;
1015 #else
1016         appData.zippyPlay = FALSE;
1017         appData.zippyTalk = FALSE;
1018         appData.noChessProgram = TRUE;
1019 #endif
1020         if (*appData.icsHelper != NULLCHAR) {
1021             appData.useTelnet = TRUE;
1022             appData.telnetProgram = appData.icsHelper;
1023         }
1024     } else {
1025         appData.zippyTalk = appData.zippyPlay = FALSE;
1026     }
1027
1028     /* [AS] Initialize pv info list [HGM] and game state */
1029     {
1030         int i, j;
1031
1032         for( i=0; i<=framePtr; i++ ) {
1033             pvInfoList[i].depth = -1;
1034             boards[i][EP_STATUS] = EP_NONE;
1035             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1036         }
1037     }
1038
1039     InitTimeControls();
1040
1041     /* [AS] Adjudication threshold */
1042     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1043
1044     InitEngine(&first, 0);
1045     InitEngine(&second, 1);
1046     CommonEngineInit();
1047
1048     pairing.which = "pairing"; // pairing engine
1049     pairing.pr = NoProc;
1050     pairing.isr = NULL;
1051     pairing.program = appData.pairingEngine;
1052     pairing.host = "localhost";
1053     pairing.dir = ".";
1054
1055     if (appData.icsActive) {
1056         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1057     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1058         appData.clockMode = FALSE;
1059         first.sendTime = second.sendTime = 0;
1060     }
1061
1062 #if ZIPPY
1063     /* Override some settings from environment variables, for backward
1064        compatibility.  Unfortunately it's not feasible to have the env
1065        vars just set defaults, at least in xboard.  Ugh.
1066     */
1067     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1068       ZippyInit();
1069     }
1070 #endif
1071
1072     if (!appData.icsActive) {
1073       char buf[MSG_SIZ];
1074       int len;
1075
1076       /* Check for variants that are supported only in ICS mode,
1077          or not at all.  Some that are accepted here nevertheless
1078          have bugs; see comments below.
1079       */
1080       VariantClass variant = StringToVariant(appData.variant);
1081       switch (variant) {
1082       case VariantBughouse:     /* need four players and two boards */
1083       case VariantKriegspiel:   /* need to hide pieces and move details */
1084         /* case VariantFischeRandom: (Fabien: moved below) */
1085         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1086         if( (len >= MSG_SIZ) && appData.debugMode )
1087           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1088
1089         DisplayFatalError(buf, 0, 2);
1090         return;
1091
1092       case VariantUnknown:
1093       case VariantLoadable:
1094       case Variant29:
1095       case Variant30:
1096       case Variant31:
1097       case Variant32:
1098       case Variant33:
1099       case Variant34:
1100       case Variant35:
1101       case Variant36:
1102       default:
1103         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1104         if( (len >= MSG_SIZ) && appData.debugMode )
1105           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1106
1107         DisplayFatalError(buf, 0, 2);
1108         return;
1109
1110       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1111       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1112       case VariantGothic:     /* [HGM] should work */
1113       case VariantCapablanca: /* [HGM] should work */
1114       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1115       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1116       case VariantKnightmate: /* [HGM] should work */
1117       case VariantCylinder:   /* [HGM] untested */
1118       case VariantFalcon:     /* [HGM] untested */
1119       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1120                                  offboard interposition not understood */
1121       case VariantNormal:     /* definitely works! */
1122       case VariantWildCastle: /* pieces not automatically shuffled */
1123       case VariantNoCastle:   /* pieces not automatically shuffled */
1124       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1125       case VariantLosers:     /* should work except for win condition,
1126                                  and doesn't know captures are mandatory */
1127       case VariantSuicide:    /* should work except for win condition,
1128                                  and doesn't know captures are mandatory */
1129       case VariantGiveaway:   /* should work except for win condition,
1130                                  and doesn't know captures are mandatory */
1131       case VariantTwoKings:   /* should work */
1132       case VariantAtomic:     /* should work except for win condition */
1133       case Variant3Check:     /* should work except for win condition */
1134       case VariantShatranj:   /* should work except for all win conditions */
1135       case VariantMakruk:     /* should work except for draw countdown */
1136       case VariantBerolina:   /* might work if TestLegality is off */
1137       case VariantCapaRandom: /* should work */
1138       case VariantJanus:      /* should work */
1139       case VariantSuper:      /* experimental */
1140       case VariantGreat:      /* experimental, requires legality testing to be off */
1141       case VariantSChess:     /* S-Chess, should work */
1142       case VariantGrand:      /* should work */
1143       case VariantSpartan:    /* should work */
1144         break;
1145       }
1146     }
1147
1148 }
1149
1150 int
1151 NextIntegerFromString (char ** str, long * value)
1152 {
1153     int result = -1;
1154     char * s = *str;
1155
1156     while( *s == ' ' || *s == '\t' ) {
1157         s++;
1158     }
1159
1160     *value = 0;
1161
1162     if( *s >= '0' && *s <= '9' ) {
1163         while( *s >= '0' && *s <= '9' ) {
1164             *value = *value * 10 + (*s - '0');
1165             s++;
1166         }
1167
1168         result = 0;
1169     }
1170
1171     *str = s;
1172
1173     return result;
1174 }
1175
1176 int
1177 NextTimeControlFromString (char ** str, long * value)
1178 {
1179     long temp;
1180     int result = NextIntegerFromString( str, &temp );
1181
1182     if( result == 0 ) {
1183         *value = temp * 60; /* Minutes */
1184         if( **str == ':' ) {
1185             (*str)++;
1186             result = NextIntegerFromString( str, &temp );
1187             *value += temp; /* Seconds */
1188         }
1189     }
1190
1191     return result;
1192 }
1193
1194 int
1195 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1196 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1197     int result = -1, type = 0; long temp, temp2;
1198
1199     if(**str != ':') return -1; // old params remain in force!
1200     (*str)++;
1201     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1202     if( NextIntegerFromString( str, &temp ) ) return -1;
1203     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1204
1205     if(**str != '/') {
1206         /* time only: incremental or sudden-death time control */
1207         if(**str == '+') { /* increment follows; read it */
1208             (*str)++;
1209             if(**str == '!') type = *(*str)++; // Bronstein TC
1210             if(result = NextIntegerFromString( str, &temp2)) return -1;
1211             *inc = temp2 * 1000;
1212             if(**str == '.') { // read fraction of increment
1213                 char *start = ++(*str);
1214                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1215                 temp2 *= 1000;
1216                 while(start++ < *str) temp2 /= 10;
1217                 *inc += temp2;
1218             }
1219         } else *inc = 0;
1220         *moves = 0; *tc = temp * 1000; *incType = type;
1221         return 0;
1222     }
1223
1224     (*str)++; /* classical time control */
1225     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1226
1227     if(result == 0) {
1228         *moves = temp;
1229         *tc    = temp2 * 1000;
1230         *inc   = 0;
1231         *incType = type;
1232     }
1233     return result;
1234 }
1235
1236 int
1237 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1238 {   /* [HGM] get time to add from the multi-session time-control string */
1239     int incType, moves=1; /* kludge to force reading of first session */
1240     long time, increment;
1241     char *s = tcString;
1242
1243     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1244     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1245     do {
1246         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1247         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1248         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1249         if(movenr == -1) return time;    /* last move before new session     */
1250         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1251         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1252         if(!moves) return increment;     /* current session is incremental   */
1253         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1254     } while(movenr >= -1);               /* try again for next session       */
1255
1256     return 0; // no new time quota on this move
1257 }
1258
1259 int
1260 ParseTimeControl (char *tc, float ti, int mps)
1261 {
1262   long tc1;
1263   long tc2;
1264   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1265   int min, sec=0;
1266
1267   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1268   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1269       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1270   if(ti > 0) {
1271
1272     if(mps)
1273       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1274     else 
1275       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1276   } else {
1277     if(mps)
1278       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1279     else 
1280       snprintf(buf, MSG_SIZ, ":%s", mytc);
1281   }
1282   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1283   
1284   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1285     return FALSE;
1286   }
1287
1288   if( *tc == '/' ) {
1289     /* Parse second time control */
1290     tc++;
1291
1292     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1293       return FALSE;
1294     }
1295
1296     if( tc2 == 0 ) {
1297       return FALSE;
1298     }
1299
1300     timeControl_2 = tc2 * 1000;
1301   }
1302   else {
1303     timeControl_2 = 0;
1304   }
1305
1306   if( tc1 == 0 ) {
1307     return FALSE;
1308   }
1309
1310   timeControl = tc1 * 1000;
1311
1312   if (ti >= 0) {
1313     timeIncrement = ti * 1000;  /* convert to ms */
1314     movesPerSession = 0;
1315   } else {
1316     timeIncrement = 0;
1317     movesPerSession = mps;
1318   }
1319   return TRUE;
1320 }
1321
1322 void
1323 InitBackEnd2 ()
1324 {
1325     if (appData.debugMode) {
1326         fprintf(debugFP, "%s\n", programVersion);
1327     }
1328
1329     set_cont_sequence(appData.wrapContSeq);
1330     if (appData.matchGames > 0) {
1331         appData.matchMode = TRUE;
1332     } else if (appData.matchMode) {
1333         appData.matchGames = 1;
1334     }
1335     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1336         appData.matchGames = appData.sameColorGames;
1337     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1338         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1339         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1340     }
1341     Reset(TRUE, FALSE);
1342     if (appData.noChessProgram || first.protocolVersion == 1) {
1343       InitBackEnd3();
1344     } else {
1345       /* kludge: allow timeout for initial "feature" commands */
1346       FreezeUI();
1347       DisplayMessage("", _("Starting chess program"));
1348       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1349     }
1350 }
1351
1352 int
1353 CalculateIndex (int index, int gameNr)
1354 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1355     int res;
1356     if(index > 0) return index; // fixed nmber
1357     if(index == 0) return 1;
1358     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1359     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1360     return res;
1361 }
1362
1363 int
1364 LoadGameOrPosition (int gameNr)
1365 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1366     if (*appData.loadGameFile != NULLCHAR) {
1367         if (!LoadGameFromFile(appData.loadGameFile,
1368                 CalculateIndex(appData.loadGameIndex, gameNr),
1369                               appData.loadGameFile, FALSE)) {
1370             DisplayFatalError(_("Bad game file"), 0, 1);
1371             return 0;
1372         }
1373     } else if (*appData.loadPositionFile != NULLCHAR) {
1374         if (!LoadPositionFromFile(appData.loadPositionFile,
1375                 CalculateIndex(appData.loadPositionIndex, gameNr),
1376                                   appData.loadPositionFile)) {
1377             DisplayFatalError(_("Bad position file"), 0, 1);
1378             return 0;
1379         }
1380     }
1381     return 1;
1382 }
1383
1384 void
1385 ReserveGame (int gameNr, char resChar)
1386 {
1387     FILE *tf = fopen(appData.tourneyFile, "r+");
1388     char *p, *q, c, buf[MSG_SIZ];
1389     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1390     safeStrCpy(buf, lastMsg, MSG_SIZ);
1391     DisplayMessage(_("Pick new game"), "");
1392     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1393     ParseArgsFromFile(tf);
1394     p = q = appData.results;
1395     if(appData.debugMode) {
1396       char *r = appData.participants;
1397       fprintf(debugFP, "results = '%s'\n", p);
1398       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1399       fprintf(debugFP, "\n");
1400     }
1401     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1402     nextGame = q - p;
1403     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1404     safeStrCpy(q, p, strlen(p) + 2);
1405     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1406     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1407     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1408         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1409         q[nextGame] = '*';
1410     }
1411     fseek(tf, -(strlen(p)+4), SEEK_END);
1412     c = fgetc(tf);
1413     if(c != '"') // depending on DOS or Unix line endings we can be one off
1414          fseek(tf, -(strlen(p)+2), SEEK_END);
1415     else fseek(tf, -(strlen(p)+3), SEEK_END);
1416     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1417     DisplayMessage(buf, "");
1418     free(p); appData.results = q;
1419     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1420        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1421       int round = appData.defaultMatchGames * appData.tourneyType;
1422       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1423          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1424         UnloadEngine(&first);  // next game belongs to other pairing;
1425         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1426     }
1427     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d, procs=(%x,%x)\n", nextGame, gameNr, first.pr, second.pr);
1428 }
1429
1430 void
1431 MatchEvent (int mode)
1432 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1433         int dummy;
1434         if(matchMode) { // already in match mode: switch it off
1435             abortMatch = TRUE;
1436             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1437             return;
1438         }
1439 //      if(gameMode != BeginningOfGame) {
1440 //          DisplayError(_("You can only start a match from the initial position."), 0);
1441 //          return;
1442 //      }
1443         abortMatch = FALSE;
1444         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1445         /* Set up machine vs. machine match */
1446         nextGame = 0;
1447         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1448         if(appData.tourneyFile[0]) {
1449             ReserveGame(-1, 0);
1450             if(nextGame > appData.matchGames) {
1451                 char buf[MSG_SIZ];
1452                 if(strchr(appData.results, '*') == NULL) {
1453                     FILE *f;
1454                     appData.tourneyCycles++;
1455                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1456                         fclose(f);
1457                         NextTourneyGame(-1, &dummy);
1458                         ReserveGame(-1, 0);
1459                         if(nextGame <= appData.matchGames) {
1460                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1461                             matchMode = mode;
1462                             ScheduleDelayedEvent(NextMatchGame, 10000);
1463                             return;
1464                         }
1465                     }
1466                 }
1467                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1468                 DisplayError(buf, 0);
1469                 appData.tourneyFile[0] = 0;
1470                 return;
1471             }
1472         } else
1473         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1474             DisplayFatalError(_("Can't have a match with no chess programs"),
1475                               0, 2);
1476             return;
1477         }
1478         matchMode = mode;
1479         matchGame = roundNr = 1;
1480         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1481         NextMatchGame();
1482 }
1483
1484 void
1485 InitBackEnd3 P((void))
1486 {
1487     GameMode initialMode;
1488     char buf[MSG_SIZ];
1489     int err, len;
1490
1491     InitChessProgram(&first, startedFromSetupPosition);
1492
1493     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1494         free(programVersion);
1495         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1496         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1497         FloatToFront(&appData.recentEngineList, appData.firstChessProgram);
1498     }
1499
1500     if (appData.icsActive) {
1501 #ifdef WIN32
1502         /* [DM] Make a console window if needed [HGM] merged ifs */
1503         ConsoleCreate();
1504 #endif
1505         err = establish();
1506         if (err != 0)
1507           {
1508             if (*appData.icsCommPort != NULLCHAR)
1509               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1510                              appData.icsCommPort);
1511             else
1512               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1513                         appData.icsHost, appData.icsPort);
1514
1515             if( (len >= MSG_SIZ) && appData.debugMode )
1516               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1517
1518             DisplayFatalError(buf, err, 1);
1519             return;
1520         }
1521         SetICSMode();
1522         telnetISR =
1523           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1524         fromUserISR =
1525           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1526         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1527             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1528     } else if (appData.noChessProgram) {
1529         SetNCPMode();
1530     } else {
1531         SetGNUMode();
1532     }
1533
1534     if (*appData.cmailGameName != NULLCHAR) {
1535         SetCmailMode();
1536         OpenLoopback(&cmailPR);
1537         cmailISR =
1538           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1539     }
1540
1541     ThawUI();
1542     DisplayMessage("", "");
1543     if (StrCaseCmp(appData.initialMode, "") == 0) {
1544       initialMode = BeginningOfGame;
1545       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1546         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1547         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1548         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1549         ModeHighlight();
1550       }
1551     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1552       initialMode = TwoMachinesPlay;
1553     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1554       initialMode = AnalyzeFile;
1555     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1556       initialMode = AnalyzeMode;
1557     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1558       initialMode = MachinePlaysWhite;
1559     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1560       initialMode = MachinePlaysBlack;
1561     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1562       initialMode = EditGame;
1563     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1564       initialMode = EditPosition;
1565     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1566       initialMode = Training;
1567     } else {
1568       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1569       if( (len >= MSG_SIZ) && appData.debugMode )
1570         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1571
1572       DisplayFatalError(buf, 0, 2);
1573       return;
1574     }
1575
1576     if (appData.matchMode) {
1577         if(appData.tourneyFile[0]) { // start tourney from command line
1578             FILE *f;
1579             if(f = fopen(appData.tourneyFile, "r")) {
1580                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1581                 fclose(f);
1582                 appData.clockMode = TRUE;
1583                 SetGNUMode();
1584             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1585         }
1586         MatchEvent(TRUE);
1587     } else if (*appData.cmailGameName != NULLCHAR) {
1588         /* Set up cmail mode */
1589         ReloadCmailMsgEvent(TRUE);
1590     } else {
1591         /* Set up other modes */
1592         if (initialMode == AnalyzeFile) {
1593           if (*appData.loadGameFile == NULLCHAR) {
1594             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1595             return;
1596           }
1597         }
1598         if (*appData.loadGameFile != NULLCHAR) {
1599             (void) LoadGameFromFile(appData.loadGameFile,
1600                                     appData.loadGameIndex,
1601                                     appData.loadGameFile, TRUE);
1602         } else if (*appData.loadPositionFile != NULLCHAR) {
1603             (void) LoadPositionFromFile(appData.loadPositionFile,
1604                                         appData.loadPositionIndex,
1605                                         appData.loadPositionFile);
1606             /* [HGM] try to make self-starting even after FEN load */
1607             /* to allow automatic setup of fairy variants with wtm */
1608             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1609                 gameMode = BeginningOfGame;
1610                 setboardSpoiledMachineBlack = 1;
1611             }
1612             /* [HGM] loadPos: make that every new game uses the setup */
1613             /* from file as long as we do not switch variant          */
1614             if(!blackPlaysFirst) {
1615                 startedFromPositionFile = TRUE;
1616                 CopyBoard(filePosition, boards[0]);
1617             }
1618         }
1619         if (initialMode == AnalyzeMode) {
1620           if (appData.noChessProgram) {
1621             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1622             return;
1623           }
1624           if (appData.icsActive) {
1625             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1626             return;
1627           }
1628           AnalyzeModeEvent();
1629         } else if (initialMode == AnalyzeFile) {
1630           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1631           ShowThinkingEvent();
1632           AnalyzeFileEvent();
1633           AnalysisPeriodicEvent(1);
1634         } else if (initialMode == MachinePlaysWhite) {
1635           if (appData.noChessProgram) {
1636             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1637                               0, 2);
1638             return;
1639           }
1640           if (appData.icsActive) {
1641             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1642                               0, 2);
1643             return;
1644           }
1645           MachineWhiteEvent();
1646         } else if (initialMode == MachinePlaysBlack) {
1647           if (appData.noChessProgram) {
1648             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1649                               0, 2);
1650             return;
1651           }
1652           if (appData.icsActive) {
1653             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1654                               0, 2);
1655             return;
1656           }
1657           MachineBlackEvent();
1658         } else if (initialMode == TwoMachinesPlay) {
1659           if (appData.noChessProgram) {
1660             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1661                               0, 2);
1662             return;
1663           }
1664           if (appData.icsActive) {
1665             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1666                               0, 2);
1667             return;
1668           }
1669           TwoMachinesEvent();
1670         } else if (initialMode == EditGame) {
1671           EditGameEvent();
1672         } else if (initialMode == EditPosition) {
1673           EditPositionEvent();
1674         } else if (initialMode == Training) {
1675           if (*appData.loadGameFile == NULLCHAR) {
1676             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1677             return;
1678           }
1679           TrainingEvent();
1680         }
1681     }
1682 }
1683
1684 void
1685 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1686 {
1687     DisplayBook(current+1);
1688
1689     MoveHistorySet( movelist, first, last, current, pvInfoList );
1690
1691     EvalGraphSet( first, last, current, pvInfoList );
1692
1693     MakeEngineOutputTitle();
1694 }
1695
1696 /*
1697  * Establish will establish a contact to a remote host.port.
1698  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1699  *  used to talk to the host.
1700  * Returns 0 if okay, error code if not.
1701  */
1702 int
1703 establish ()
1704 {
1705     char buf[MSG_SIZ];
1706
1707     if (*appData.icsCommPort != NULLCHAR) {
1708         /* Talk to the host through a serial comm port */
1709         return OpenCommPort(appData.icsCommPort, &icsPR);
1710
1711     } else if (*appData.gateway != NULLCHAR) {
1712         if (*appData.remoteShell == NULLCHAR) {
1713             /* Use the rcmd protocol to run telnet program on a gateway host */
1714             snprintf(buf, sizeof(buf), "%s %s %s",
1715                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1716             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1717
1718         } else {
1719             /* Use the rsh program to run telnet program on a gateway host */
1720             if (*appData.remoteUser == NULLCHAR) {
1721                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1722                         appData.gateway, appData.telnetProgram,
1723                         appData.icsHost, appData.icsPort);
1724             } else {
1725                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1726                         appData.remoteShell, appData.gateway,
1727                         appData.remoteUser, appData.telnetProgram,
1728                         appData.icsHost, appData.icsPort);
1729             }
1730             return StartChildProcess(buf, "", &icsPR);
1731
1732         }
1733     } else if (appData.useTelnet) {
1734         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1735
1736     } else {
1737         /* TCP socket interface differs somewhat between
1738            Unix and NT; handle details in the front end.
1739            */
1740         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1741     }
1742 }
1743
1744 void
1745 EscapeExpand (char *p, char *q)
1746 {       // [HGM] initstring: routine to shape up string arguments
1747         while(*p++ = *q++) if(p[-1] == '\\')
1748             switch(*q++) {
1749                 case 'n': p[-1] = '\n'; break;
1750                 case 'r': p[-1] = '\r'; break;
1751                 case 't': p[-1] = '\t'; break;
1752                 case '\\': p[-1] = '\\'; break;
1753                 case 0: *p = 0; return;
1754                 default: p[-1] = q[-1]; break;
1755             }
1756 }
1757
1758 void
1759 show_bytes (FILE *fp, char *buf, int count)
1760 {
1761     while (count--) {
1762         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1763             fprintf(fp, "\\%03o", *buf & 0xff);
1764         } else {
1765             putc(*buf, fp);
1766         }
1767         buf++;
1768     }
1769     fflush(fp);
1770 }
1771
1772 /* Returns an errno value */
1773 int
1774 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1775 {
1776     char buf[8192], *p, *q, *buflim;
1777     int left, newcount, outcount;
1778
1779     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1780         *appData.gateway != NULLCHAR) {
1781         if (appData.debugMode) {
1782             fprintf(debugFP, ">ICS: ");
1783             show_bytes(debugFP, message, count);
1784             fprintf(debugFP, "\n");
1785         }
1786         return OutputToProcess(pr, message, count, outError);
1787     }
1788
1789     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1790     p = message;
1791     q = buf;
1792     left = count;
1793     newcount = 0;
1794     while (left) {
1795         if (q >= buflim) {
1796             if (appData.debugMode) {
1797                 fprintf(debugFP, ">ICS: ");
1798                 show_bytes(debugFP, buf, newcount);
1799                 fprintf(debugFP, "\n");
1800             }
1801             outcount = OutputToProcess(pr, buf, newcount, outError);
1802             if (outcount < newcount) return -1; /* to be sure */
1803             q = buf;
1804             newcount = 0;
1805         }
1806         if (*p == '\n') {
1807             *q++ = '\r';
1808             newcount++;
1809         } else if (((unsigned char) *p) == TN_IAC) {
1810             *q++ = (char) TN_IAC;
1811             newcount ++;
1812         }
1813         *q++ = *p++;
1814         newcount++;
1815         left--;
1816     }
1817     if (appData.debugMode) {
1818         fprintf(debugFP, ">ICS: ");
1819         show_bytes(debugFP, buf, newcount);
1820         fprintf(debugFP, "\n");
1821     }
1822     outcount = OutputToProcess(pr, buf, newcount, outError);
1823     if (outcount < newcount) return -1; /* to be sure */
1824     return count;
1825 }
1826
1827 void
1828 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1829 {
1830     int outError, outCount;
1831     static int gotEof = 0;
1832
1833     /* Pass data read from player on to ICS */
1834     if (count > 0) {
1835         gotEof = 0;
1836         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1837         if (outCount < count) {
1838             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1839         }
1840     } else if (count < 0) {
1841         RemoveInputSource(isr);
1842         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1843     } else if (gotEof++ > 0) {
1844         RemoveInputSource(isr);
1845         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1846     }
1847 }
1848
1849 void
1850 KeepAlive ()
1851 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1852     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1853     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1854     SendToICS("date\n");
1855     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1856 }
1857
1858 /* added routine for printf style output to ics */
1859 void
1860 ics_printf (char *format, ...)
1861 {
1862     char buffer[MSG_SIZ];
1863     va_list args;
1864
1865     va_start(args, format);
1866     vsnprintf(buffer, sizeof(buffer), format, args);
1867     buffer[sizeof(buffer)-1] = '\0';
1868     SendToICS(buffer);
1869     va_end(args);
1870 }
1871
1872 void
1873 SendToICS (char *s)
1874 {
1875     int count, outCount, outError;
1876
1877     if (icsPR == NoProc) return;
1878
1879     count = strlen(s);
1880     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1881     if (outCount < count) {
1882         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1883     }
1884 }
1885
1886 /* This is used for sending logon scripts to the ICS. Sending
1887    without a delay causes problems when using timestamp on ICC
1888    (at least on my machine). */
1889 void
1890 SendToICSDelayed (char *s, long msdelay)
1891 {
1892     int count, outCount, outError;
1893
1894     if (icsPR == NoProc) return;
1895
1896     count = strlen(s);
1897     if (appData.debugMode) {
1898         fprintf(debugFP, ">ICS: ");
1899         show_bytes(debugFP, s, count);
1900         fprintf(debugFP, "\n");
1901     }
1902     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1903                                       msdelay);
1904     if (outCount < count) {
1905         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1906     }
1907 }
1908
1909
1910 /* Remove all highlighting escape sequences in s
1911    Also deletes any suffix starting with '('
1912    */
1913 char *
1914 StripHighlightAndTitle (char *s)
1915 {
1916     static char retbuf[MSG_SIZ];
1917     char *p = retbuf;
1918
1919     while (*s != NULLCHAR) {
1920         while (*s == '\033') {
1921             while (*s != NULLCHAR && !isalpha(*s)) s++;
1922             if (*s != NULLCHAR) s++;
1923         }
1924         while (*s != NULLCHAR && *s != '\033') {
1925             if (*s == '(' || *s == '[') {
1926                 *p = NULLCHAR;
1927                 return retbuf;
1928             }
1929             *p++ = *s++;
1930         }
1931     }
1932     *p = NULLCHAR;
1933     return retbuf;
1934 }
1935
1936 /* Remove all highlighting escape sequences in s */
1937 char *
1938 StripHighlight (char *s)
1939 {
1940     static char retbuf[MSG_SIZ];
1941     char *p = retbuf;
1942
1943     while (*s != NULLCHAR) {
1944         while (*s == '\033') {
1945             while (*s != NULLCHAR && !isalpha(*s)) s++;
1946             if (*s != NULLCHAR) s++;
1947         }
1948         while (*s != NULLCHAR && *s != '\033') {
1949             *p++ = *s++;
1950         }
1951     }
1952     *p = NULLCHAR;
1953     return retbuf;
1954 }
1955
1956 char *variantNames[] = VARIANT_NAMES;
1957 char *
1958 VariantName (VariantClass v)
1959 {
1960     return variantNames[v];
1961 }
1962
1963
1964 /* Identify a variant from the strings the chess servers use or the
1965    PGN Variant tag names we use. */
1966 VariantClass
1967 StringToVariant (char *e)
1968 {
1969     char *p;
1970     int wnum = -1;
1971     VariantClass v = VariantNormal;
1972     int i, found = FALSE;
1973     char buf[MSG_SIZ];
1974     int len;
1975
1976     if (!e) return v;
1977
1978     /* [HGM] skip over optional board-size prefixes */
1979     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1980         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1981         while( *e++ != '_');
1982     }
1983
1984     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1985         v = VariantNormal;
1986         found = TRUE;
1987     } else
1988     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1989       if (StrCaseStr(e, variantNames[i])) {
1990         v = (VariantClass) i;
1991         found = TRUE;
1992         break;
1993       }
1994     }
1995
1996     if (!found) {
1997       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1998           || StrCaseStr(e, "wild/fr")
1999           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2000         v = VariantFischeRandom;
2001       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2002                  (i = 1, p = StrCaseStr(e, "w"))) {
2003         p += i;
2004         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2005         if (isdigit(*p)) {
2006           wnum = atoi(p);
2007         } else {
2008           wnum = -1;
2009         }
2010         switch (wnum) {
2011         case 0: /* FICS only, actually */
2012         case 1:
2013           /* Castling legal even if K starts on d-file */
2014           v = VariantWildCastle;
2015           break;
2016         case 2:
2017         case 3:
2018         case 4:
2019           /* Castling illegal even if K & R happen to start in
2020              normal positions. */
2021           v = VariantNoCastle;
2022           break;
2023         case 5:
2024         case 7:
2025         case 8:
2026         case 10:
2027         case 11:
2028         case 12:
2029         case 13:
2030         case 14:
2031         case 15:
2032         case 18:
2033         case 19:
2034           /* Castling legal iff K & R start in normal positions */
2035           v = VariantNormal;
2036           break;
2037         case 6:
2038         case 20:
2039         case 21:
2040           /* Special wilds for position setup; unclear what to do here */
2041           v = VariantLoadable;
2042           break;
2043         case 9:
2044           /* Bizarre ICC game */
2045           v = VariantTwoKings;
2046           break;
2047         case 16:
2048           v = VariantKriegspiel;
2049           break;
2050         case 17:
2051           v = VariantLosers;
2052           break;
2053         case 22:
2054           v = VariantFischeRandom;
2055           break;
2056         case 23:
2057           v = VariantCrazyhouse;
2058           break;
2059         case 24:
2060           v = VariantBughouse;
2061           break;
2062         case 25:
2063           v = Variant3Check;
2064           break;
2065         case 26:
2066           /* Not quite the same as FICS suicide! */
2067           v = VariantGiveaway;
2068           break;
2069         case 27:
2070           v = VariantAtomic;
2071           break;
2072         case 28:
2073           v = VariantShatranj;
2074           break;
2075
2076         /* Temporary names for future ICC types.  The name *will* change in
2077            the next xboard/WinBoard release after ICC defines it. */
2078         case 29:
2079           v = Variant29;
2080           break;
2081         case 30:
2082           v = Variant30;
2083           break;
2084         case 31:
2085           v = Variant31;
2086           break;
2087         case 32:
2088           v = Variant32;
2089           break;
2090         case 33:
2091           v = Variant33;
2092           break;
2093         case 34:
2094           v = Variant34;
2095           break;
2096         case 35:
2097           v = Variant35;
2098           break;
2099         case 36:
2100           v = Variant36;
2101           break;
2102         case 37:
2103           v = VariantShogi;
2104           break;
2105         case 38:
2106           v = VariantXiangqi;
2107           break;
2108         case 39:
2109           v = VariantCourier;
2110           break;
2111         case 40:
2112           v = VariantGothic;
2113           break;
2114         case 41:
2115           v = VariantCapablanca;
2116           break;
2117         case 42:
2118           v = VariantKnightmate;
2119           break;
2120         case 43:
2121           v = VariantFairy;
2122           break;
2123         case 44:
2124           v = VariantCylinder;
2125           break;
2126         case 45:
2127           v = VariantFalcon;
2128           break;
2129         case 46:
2130           v = VariantCapaRandom;
2131           break;
2132         case 47:
2133           v = VariantBerolina;
2134           break;
2135         case 48:
2136           v = VariantJanus;
2137           break;
2138         case 49:
2139           v = VariantSuper;
2140           break;
2141         case 50:
2142           v = VariantGreat;
2143           break;
2144         case -1:
2145           /* Found "wild" or "w" in the string but no number;
2146              must assume it's normal chess. */
2147           v = VariantNormal;
2148           break;
2149         default:
2150           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2151           if( (len >= MSG_SIZ) && appData.debugMode )
2152             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2153
2154           DisplayError(buf, 0);
2155           v = VariantUnknown;
2156           break;
2157         }
2158       }
2159     }
2160     if (appData.debugMode) {
2161       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2162               e, wnum, VariantName(v));
2163     }
2164     return v;
2165 }
2166
2167 static int leftover_start = 0, leftover_len = 0;
2168 char star_match[STAR_MATCH_N][MSG_SIZ];
2169
2170 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2171    advance *index beyond it, and set leftover_start to the new value of
2172    *index; else return FALSE.  If pattern contains the character '*', it
2173    matches any sequence of characters not containing '\r', '\n', or the
2174    character following the '*' (if any), and the matched sequence(s) are
2175    copied into star_match.
2176    */
2177 int
2178 looking_at ( char *buf, int *index, char *pattern)
2179 {
2180     char *bufp = &buf[*index], *patternp = pattern;
2181     int star_count = 0;
2182     char *matchp = star_match[0];
2183
2184     for (;;) {
2185         if (*patternp == NULLCHAR) {
2186             *index = leftover_start = bufp - buf;
2187             *matchp = NULLCHAR;
2188             return TRUE;
2189         }
2190         if (*bufp == NULLCHAR) return FALSE;
2191         if (*patternp == '*') {
2192             if (*bufp == *(patternp + 1)) {
2193                 *matchp = NULLCHAR;
2194                 matchp = star_match[++star_count];
2195                 patternp += 2;
2196                 bufp++;
2197                 continue;
2198             } else if (*bufp == '\n' || *bufp == '\r') {
2199                 patternp++;
2200                 if (*patternp == NULLCHAR)
2201                   continue;
2202                 else
2203                   return FALSE;
2204             } else {
2205                 *matchp++ = *bufp++;
2206                 continue;
2207             }
2208         }
2209         if (*patternp != *bufp) return FALSE;
2210         patternp++;
2211         bufp++;
2212     }
2213 }
2214
2215 void
2216 SendToPlayer (char *data, int length)
2217 {
2218     int error, outCount;
2219     outCount = OutputToProcess(NoProc, data, length, &error);
2220     if (outCount < length) {
2221         DisplayFatalError(_("Error writing to display"), error, 1);
2222     }
2223 }
2224
2225 void
2226 PackHolding (char packed[], char *holding)
2227 {
2228     char *p = holding;
2229     char *q = packed;
2230     int runlength = 0;
2231     int curr = 9999;
2232     do {
2233         if (*p == curr) {
2234             runlength++;
2235         } else {
2236             switch (runlength) {
2237               case 0:
2238                 break;
2239               case 1:
2240                 *q++ = curr;
2241                 break;
2242               case 2:
2243                 *q++ = curr;
2244                 *q++ = curr;
2245                 break;
2246               default:
2247                 sprintf(q, "%d", runlength);
2248                 while (*q) q++;
2249                 *q++ = curr;
2250                 break;
2251             }
2252             runlength = 1;
2253             curr = *p;
2254         }
2255     } while (*p++);
2256     *q = NULLCHAR;
2257 }
2258
2259 /* Telnet protocol requests from the front end */
2260 void
2261 TelnetRequest (unsigned char ddww, unsigned char option)
2262 {
2263     unsigned char msg[3];
2264     int outCount, outError;
2265
2266     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2267
2268     if (appData.debugMode) {
2269         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2270         switch (ddww) {
2271           case TN_DO:
2272             ddwwStr = "DO";
2273             break;
2274           case TN_DONT:
2275             ddwwStr = "DONT";
2276             break;
2277           case TN_WILL:
2278             ddwwStr = "WILL";
2279             break;
2280           case TN_WONT:
2281             ddwwStr = "WONT";
2282             break;
2283           default:
2284             ddwwStr = buf1;
2285             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2286             break;
2287         }
2288         switch (option) {
2289           case TN_ECHO:
2290             optionStr = "ECHO";
2291             break;
2292           default:
2293             optionStr = buf2;
2294             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2295             break;
2296         }
2297         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2298     }
2299     msg[0] = TN_IAC;
2300     msg[1] = ddww;
2301     msg[2] = option;
2302     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2303     if (outCount < 3) {
2304         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2305     }
2306 }
2307
2308 void
2309 DoEcho ()
2310 {
2311     if (!appData.icsActive) return;
2312     TelnetRequest(TN_DO, TN_ECHO);
2313 }
2314
2315 void
2316 DontEcho ()
2317 {
2318     if (!appData.icsActive) return;
2319     TelnetRequest(TN_DONT, TN_ECHO);
2320 }
2321
2322 void
2323 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2324 {
2325     /* put the holdings sent to us by the server on the board holdings area */
2326     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2327     char p;
2328     ChessSquare piece;
2329
2330     if(gameInfo.holdingsWidth < 2)  return;
2331     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2332         return; // prevent overwriting by pre-board holdings
2333
2334     if( (int)lowestPiece >= BlackPawn ) {
2335         holdingsColumn = 0;
2336         countsColumn = 1;
2337         holdingsStartRow = BOARD_HEIGHT-1;
2338         direction = -1;
2339     } else {
2340         holdingsColumn = BOARD_WIDTH-1;
2341         countsColumn = BOARD_WIDTH-2;
2342         holdingsStartRow = 0;
2343         direction = 1;
2344     }
2345
2346     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2347         board[i][holdingsColumn] = EmptySquare;
2348         board[i][countsColumn]   = (ChessSquare) 0;
2349     }
2350     while( (p=*holdings++) != NULLCHAR ) {
2351         piece = CharToPiece( ToUpper(p) );
2352         if(piece == EmptySquare) continue;
2353         /*j = (int) piece - (int) WhitePawn;*/
2354         j = PieceToNumber(piece);
2355         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2356         if(j < 0) continue;               /* should not happen */
2357         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2358         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2359         board[holdingsStartRow+j*direction][countsColumn]++;
2360     }
2361 }
2362
2363
2364 void
2365 VariantSwitch (Board board, VariantClass newVariant)
2366 {
2367    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2368    static Board oldBoard;
2369
2370    startedFromPositionFile = FALSE;
2371    if(gameInfo.variant == newVariant) return;
2372
2373    /* [HGM] This routine is called each time an assignment is made to
2374     * gameInfo.variant during a game, to make sure the board sizes
2375     * are set to match the new variant. If that means adding or deleting
2376     * holdings, we shift the playing board accordingly
2377     * This kludge is needed because in ICS observe mode, we get boards
2378     * of an ongoing game without knowing the variant, and learn about the
2379     * latter only later. This can be because of the move list we requested,
2380     * in which case the game history is refilled from the beginning anyway,
2381     * but also when receiving holdings of a crazyhouse game. In the latter
2382     * case we want to add those holdings to the already received position.
2383     */
2384
2385
2386    if (appData.debugMode) {
2387      fprintf(debugFP, "Switch board from %s to %s\n",
2388              VariantName(gameInfo.variant), VariantName(newVariant));
2389      setbuf(debugFP, NULL);
2390    }
2391    shuffleOpenings = 0;       /* [HGM] shuffle */
2392    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2393    switch(newVariant)
2394      {
2395      case VariantShogi:
2396        newWidth = 9;  newHeight = 9;
2397        gameInfo.holdingsSize = 7;
2398      case VariantBughouse:
2399      case VariantCrazyhouse:
2400        newHoldingsWidth = 2; break;
2401      case VariantGreat:
2402        newWidth = 10;
2403      case VariantSuper:
2404        newHoldingsWidth = 2;
2405        gameInfo.holdingsSize = 8;
2406        break;
2407      case VariantGothic:
2408      case VariantCapablanca:
2409      case VariantCapaRandom:
2410        newWidth = 10;
2411      default:
2412        newHoldingsWidth = gameInfo.holdingsSize = 0;
2413      };
2414
2415    if(newWidth  != gameInfo.boardWidth  ||
2416       newHeight != gameInfo.boardHeight ||
2417       newHoldingsWidth != gameInfo.holdingsWidth ) {
2418
2419      /* shift position to new playing area, if needed */
2420      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2421        for(i=0; i<BOARD_HEIGHT; i++)
2422          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2423            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2424              board[i][j];
2425        for(i=0; i<newHeight; i++) {
2426          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2427          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2428        }
2429      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2430        for(i=0; i<BOARD_HEIGHT; i++)
2431          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2432            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2433              board[i][j];
2434      }
2435      gameInfo.boardWidth  = newWidth;
2436      gameInfo.boardHeight = newHeight;
2437      gameInfo.holdingsWidth = newHoldingsWidth;
2438      gameInfo.variant = newVariant;
2439      InitDrawingSizes(-2, 0);
2440    } else gameInfo.variant = newVariant;
2441    CopyBoard(oldBoard, board);   // remember correctly formatted board
2442      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2443    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2444 }
2445
2446 static int loggedOn = FALSE;
2447
2448 /*-- Game start info cache: --*/
2449 int gs_gamenum;
2450 char gs_kind[MSG_SIZ];
2451 static char player1Name[128] = "";
2452 static char player2Name[128] = "";
2453 static char cont_seq[] = "\n\\   ";
2454 static int player1Rating = -1;
2455 static int player2Rating = -1;
2456 /*----------------------------*/
2457
2458 ColorClass curColor = ColorNormal;
2459 int suppressKibitz = 0;
2460
2461 // [HGM] seekgraph
2462 Boolean soughtPending = FALSE;
2463 Boolean seekGraphUp;
2464 #define MAX_SEEK_ADS 200
2465 #define SQUARE 0x80
2466 char *seekAdList[MAX_SEEK_ADS];
2467 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2468 float tcList[MAX_SEEK_ADS];
2469 char colorList[MAX_SEEK_ADS];
2470 int nrOfSeekAds = 0;
2471 int minRating = 1010, maxRating = 2800;
2472 int hMargin = 10, vMargin = 20, h, w;
2473 extern int squareSize, lineGap;
2474
2475 void
2476 PlotSeekAd (int i)
2477 {
2478         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2479         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2480         if(r < minRating+100 && r >=0 ) r = minRating+100;
2481         if(r > maxRating) r = maxRating;
2482         if(tc < 1.) tc = 1.;
2483         if(tc > 95.) tc = 95.;
2484         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2485         y = ((double)r - minRating)/(maxRating - minRating)
2486             * (h-vMargin-squareSize/8-1) + vMargin;
2487         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2488         if(strstr(seekAdList[i], " u ")) color = 1;
2489         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2490            !strstr(seekAdList[i], "bullet") &&
2491            !strstr(seekAdList[i], "blitz") &&
2492            !strstr(seekAdList[i], "standard") ) color = 2;
2493         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2494         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2495 }
2496
2497 void
2498 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2499 {
2500         char buf[MSG_SIZ], *ext = "";
2501         VariantClass v = StringToVariant(type);
2502         if(strstr(type, "wild")) {
2503             ext = type + 4; // append wild number
2504             if(v == VariantFischeRandom) type = "chess960"; else
2505             if(v == VariantLoadable) type = "setup"; else
2506             type = VariantName(v);
2507         }
2508         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2509         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2510             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2511             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2512             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2513             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2514             seekNrList[nrOfSeekAds] = nr;
2515             zList[nrOfSeekAds] = 0;
2516             seekAdList[nrOfSeekAds++] = StrSave(buf);
2517             if(plot) PlotSeekAd(nrOfSeekAds-1);
2518         }
2519 }
2520
2521 void
2522 EraseSeekDot (int i)
2523 {
2524     int x = xList[i], y = yList[i], d=squareSize/4, k;
2525     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2526     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2527     // now replot every dot that overlapped
2528     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2529         int xx = xList[k], yy = yList[k];
2530         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2531             DrawSeekDot(xx, yy, colorList[k]);
2532     }
2533 }
2534
2535 void
2536 RemoveSeekAd (int nr)
2537 {
2538         int i;
2539         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2540             EraseSeekDot(i);
2541             if(seekAdList[i]) free(seekAdList[i]);
2542             seekAdList[i] = seekAdList[--nrOfSeekAds];
2543             seekNrList[i] = seekNrList[nrOfSeekAds];
2544             ratingList[i] = ratingList[nrOfSeekAds];
2545             colorList[i]  = colorList[nrOfSeekAds];
2546             tcList[i] = tcList[nrOfSeekAds];
2547             xList[i]  = xList[nrOfSeekAds];
2548             yList[i]  = yList[nrOfSeekAds];
2549             zList[i]  = zList[nrOfSeekAds];
2550             seekAdList[nrOfSeekAds] = NULL;
2551             break;
2552         }
2553 }
2554
2555 Boolean
2556 MatchSoughtLine (char *line)
2557 {
2558     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2559     int nr, base, inc, u=0; char dummy;
2560
2561     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2562        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2563        (u=1) &&
2564        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2565         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2566         // match: compact and save the line
2567         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2568         return TRUE;
2569     }
2570     return FALSE;
2571 }
2572
2573 int
2574 DrawSeekGraph ()
2575 {
2576     int i;
2577     if(!seekGraphUp) return FALSE;
2578     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2579     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2580
2581     DrawSeekBackground(0, 0, w, h);
2582     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2583     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2584     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2585         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2586         yy = h-1-yy;
2587         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2588         if(i%500 == 0) {
2589             char buf[MSG_SIZ];
2590             snprintf(buf, MSG_SIZ, "%d", i);
2591             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2592         }
2593     }
2594     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2595     for(i=1; i<100; i+=(i<10?1:5)) {
2596         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2597         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2598         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2599             char buf[MSG_SIZ];
2600             snprintf(buf, MSG_SIZ, "%d", i);
2601             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2602         }
2603     }
2604     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2605     return TRUE;
2606 }
2607
2608 int
2609 SeekGraphClick (ClickType click, int x, int y, int moving)
2610 {
2611     static int lastDown = 0, displayed = 0, lastSecond;
2612     if(y < 0) return FALSE;
2613     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2614         if(click == Release || moving) return FALSE;
2615         nrOfSeekAds = 0;
2616         soughtPending = TRUE;
2617         SendToICS(ics_prefix);
2618         SendToICS("sought\n"); // should this be "sought all"?
2619     } else { // issue challenge based on clicked ad
2620         int dist = 10000; int i, closest = 0, second = 0;
2621         for(i=0; i<nrOfSeekAds; i++) {
2622             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2623             if(d < dist) { dist = d; closest = i; }
2624             second += (d - zList[i] < 120); // count in-range ads
2625             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2626         }
2627         if(dist < 120) {
2628             char buf[MSG_SIZ];
2629             second = (second > 1);
2630             if(displayed != closest || second != lastSecond) {
2631                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2632                 lastSecond = second; displayed = closest;
2633             }
2634             if(click == Press) {
2635                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2636                 lastDown = closest;
2637                 return TRUE;
2638             } // on press 'hit', only show info
2639             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2640             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2641             SendToICS(ics_prefix);
2642             SendToICS(buf);
2643             return TRUE; // let incoming board of started game pop down the graph
2644         } else if(click == Release) { // release 'miss' is ignored
2645             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2646             if(moving == 2) { // right up-click
2647                 nrOfSeekAds = 0; // refresh graph
2648                 soughtPending = TRUE;
2649                 SendToICS(ics_prefix);
2650                 SendToICS("sought\n"); // should this be "sought all"?
2651             }
2652             return TRUE;
2653         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2654         // press miss or release hit 'pop down' seek graph
2655         seekGraphUp = FALSE;
2656         DrawPosition(TRUE, NULL);
2657     }
2658     return TRUE;
2659 }
2660
2661 void
2662 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2663 {
2664 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2665 #define STARTED_NONE 0
2666 #define STARTED_MOVES 1
2667 #define STARTED_BOARD 2
2668 #define STARTED_OBSERVE 3
2669 #define STARTED_HOLDINGS 4
2670 #define STARTED_CHATTER 5
2671 #define STARTED_COMMENT 6
2672 #define STARTED_MOVES_NOHIDE 7
2673
2674     static int started = STARTED_NONE;
2675     static char parse[20000];
2676     static int parse_pos = 0;
2677     static char buf[BUF_SIZE + 1];
2678     static int firstTime = TRUE, intfSet = FALSE;
2679     static ColorClass prevColor = ColorNormal;
2680     static int savingComment = FALSE;
2681     static int cmatch = 0; // continuation sequence match
2682     char *bp;
2683     char str[MSG_SIZ];
2684     int i, oldi;
2685     int buf_len;
2686     int next_out;
2687     int tkind;
2688     int backup;    /* [DM] For zippy color lines */
2689     char *p;
2690     char talker[MSG_SIZ]; // [HGM] chat
2691     int channel;
2692
2693     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2694
2695     if (appData.debugMode) {
2696       if (!error) {
2697         fprintf(debugFP, "<ICS: ");
2698         show_bytes(debugFP, data, count);
2699         fprintf(debugFP, "\n");
2700       }
2701     }
2702
2703     if (appData.debugMode) { int f = forwardMostMove;
2704         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2705                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2706                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2707     }
2708     if (count > 0) {
2709         /* If last read ended with a partial line that we couldn't parse,
2710            prepend it to the new read and try again. */
2711         if (leftover_len > 0) {
2712             for (i=0; i<leftover_len; i++)
2713               buf[i] = buf[leftover_start + i];
2714         }
2715
2716     /* copy new characters into the buffer */
2717     bp = buf + leftover_len;
2718     buf_len=leftover_len;
2719     for (i=0; i<count; i++)
2720     {
2721         // ignore these
2722         if (data[i] == '\r')
2723             continue;
2724
2725         // join lines split by ICS?
2726         if (!appData.noJoin)
2727         {
2728             /*
2729                 Joining just consists of finding matches against the
2730                 continuation sequence, and discarding that sequence
2731                 if found instead of copying it.  So, until a match
2732                 fails, there's nothing to do since it might be the
2733                 complete sequence, and thus, something we don't want
2734                 copied.
2735             */
2736             if (data[i] == cont_seq[cmatch])
2737             {
2738                 cmatch++;
2739                 if (cmatch == strlen(cont_seq))
2740                 {
2741                     cmatch = 0; // complete match.  just reset the counter
2742
2743                     /*
2744                         it's possible for the ICS to not include the space
2745                         at the end of the last word, making our [correct]
2746                         join operation fuse two separate words.  the server
2747                         does this when the space occurs at the width setting.
2748                     */
2749                     if (!buf_len || buf[buf_len-1] != ' ')
2750                     {
2751                         *bp++ = ' ';
2752                         buf_len++;
2753                     }
2754                 }
2755                 continue;
2756             }
2757             else if (cmatch)
2758             {
2759                 /*
2760                     match failed, so we have to copy what matched before
2761                     falling through and copying this character.  In reality,
2762                     this will only ever be just the newline character, but
2763                     it doesn't hurt to be precise.
2764                 */
2765                 strncpy(bp, cont_seq, cmatch);
2766                 bp += cmatch;
2767                 buf_len += cmatch;
2768                 cmatch = 0;
2769             }
2770         }
2771
2772         // copy this char
2773         *bp++ = data[i];
2774         buf_len++;
2775     }
2776
2777         buf[buf_len] = NULLCHAR;
2778 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2779         next_out = 0;
2780         leftover_start = 0;
2781
2782         i = 0;
2783         while (i < buf_len) {
2784             /* Deal with part of the TELNET option negotiation
2785                protocol.  We refuse to do anything beyond the
2786                defaults, except that we allow the WILL ECHO option,
2787                which ICS uses to turn off password echoing when we are
2788                directly connected to it.  We reject this option
2789                if localLineEditing mode is on (always on in xboard)
2790                and we are talking to port 23, which might be a real
2791                telnet server that will try to keep WILL ECHO on permanently.
2792              */
2793             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2794                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2795                 unsigned char option;
2796                 oldi = i;
2797                 switch ((unsigned char) buf[++i]) {
2798                   case TN_WILL:
2799                     if (appData.debugMode)
2800                       fprintf(debugFP, "\n<WILL ");
2801                     switch (option = (unsigned char) buf[++i]) {
2802                       case TN_ECHO:
2803                         if (appData.debugMode)
2804                           fprintf(debugFP, "ECHO ");
2805                         /* Reply only if this is a change, according
2806                            to the protocol rules. */
2807                         if (remoteEchoOption) break;
2808                         if (appData.localLineEditing &&
2809                             atoi(appData.icsPort) == TN_PORT) {
2810                             TelnetRequest(TN_DONT, TN_ECHO);
2811                         } else {
2812                             EchoOff();
2813                             TelnetRequest(TN_DO, TN_ECHO);
2814                             remoteEchoOption = TRUE;
2815                         }
2816                         break;
2817                       default:
2818                         if (appData.debugMode)
2819                           fprintf(debugFP, "%d ", option);
2820                         /* Whatever this is, we don't want it. */
2821                         TelnetRequest(TN_DONT, option);
2822                         break;
2823                     }
2824                     break;
2825                   case TN_WONT:
2826                     if (appData.debugMode)
2827                       fprintf(debugFP, "\n<WONT ");
2828                     switch (option = (unsigned char) buf[++i]) {
2829                       case TN_ECHO:
2830                         if (appData.debugMode)
2831                           fprintf(debugFP, "ECHO ");
2832                         /* Reply only if this is a change, according
2833                            to the protocol rules. */
2834                         if (!remoteEchoOption) break;
2835                         EchoOn();
2836                         TelnetRequest(TN_DONT, TN_ECHO);
2837                         remoteEchoOption = FALSE;
2838                         break;
2839                       default:
2840                         if (appData.debugMode)
2841                           fprintf(debugFP, "%d ", (unsigned char) option);
2842                         /* Whatever this is, it must already be turned
2843                            off, because we never agree to turn on
2844                            anything non-default, so according to the
2845                            protocol rules, we don't reply. */
2846                         break;
2847                     }
2848                     break;
2849                   case TN_DO:
2850                     if (appData.debugMode)
2851                       fprintf(debugFP, "\n<DO ");
2852                     switch (option = (unsigned char) buf[++i]) {
2853                       default:
2854                         /* Whatever this is, we refuse to do it. */
2855                         if (appData.debugMode)
2856                           fprintf(debugFP, "%d ", option);
2857                         TelnetRequest(TN_WONT, option);
2858                         break;
2859                     }
2860                     break;
2861                   case TN_DONT:
2862                     if (appData.debugMode)
2863                       fprintf(debugFP, "\n<DONT ");
2864                     switch (option = (unsigned char) buf[++i]) {
2865                       default:
2866                         if (appData.debugMode)
2867                           fprintf(debugFP, "%d ", option);
2868                         /* Whatever this is, we are already not doing
2869                            it, because we never agree to do anything
2870                            non-default, so according to the protocol
2871                            rules, we don't reply. */
2872                         break;
2873                     }
2874                     break;
2875                   case TN_IAC:
2876                     if (appData.debugMode)
2877                       fprintf(debugFP, "\n<IAC ");
2878                     /* Doubled IAC; pass it through */
2879                     i--;
2880                     break;
2881                   default:
2882                     if (appData.debugMode)
2883                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2884                     /* Drop all other telnet commands on the floor */
2885                     break;
2886                 }
2887                 if (oldi > next_out)
2888                   SendToPlayer(&buf[next_out], oldi - next_out);
2889                 if (++i > next_out)
2890                   next_out = i;
2891                 continue;
2892             }
2893
2894             /* OK, this at least will *usually* work */
2895             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2896                 loggedOn = TRUE;
2897             }
2898
2899             if (loggedOn && !intfSet) {
2900                 if (ics_type == ICS_ICC) {
2901                   snprintf(str, MSG_SIZ,
2902                           "/set-quietly interface %s\n/set-quietly style 12\n",
2903                           programVersion);
2904                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2905                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2906                 } else if (ics_type == ICS_CHESSNET) {
2907                   snprintf(str, MSG_SIZ, "/style 12\n");
2908                 } else {
2909                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2910                   strcat(str, programVersion);
2911                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2912                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2913                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2914 #ifdef WIN32
2915                   strcat(str, "$iset nohighlight 1\n");
2916 #endif
2917                   strcat(str, "$iset lock 1\n$style 12\n");
2918                 }
2919                 SendToICS(str);
2920                 NotifyFrontendLogin();
2921                 intfSet = TRUE;
2922             }
2923
2924             if (started == STARTED_COMMENT) {
2925                 /* Accumulate characters in comment */
2926                 parse[parse_pos++] = buf[i];
2927                 if (buf[i] == '\n') {
2928                     parse[parse_pos] = NULLCHAR;
2929                     if(chattingPartner>=0) {
2930                         char mess[MSG_SIZ];
2931                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2932                         OutputChatMessage(chattingPartner, mess);
2933                         chattingPartner = -1;
2934                         next_out = i+1; // [HGM] suppress printing in ICS window
2935                     } else
2936                     if(!suppressKibitz) // [HGM] kibitz
2937                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2938                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2939                         int nrDigit = 0, nrAlph = 0, j;
2940                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2941                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2942                         parse[parse_pos] = NULLCHAR;
2943                         // try to be smart: if it does not look like search info, it should go to
2944                         // ICS interaction window after all, not to engine-output window.
2945                         for(j=0; j<parse_pos; j++) { // count letters and digits
2946                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2947                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2948                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2949                         }
2950                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2951                             int depth=0; float score;
2952                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2953                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2954                                 pvInfoList[forwardMostMove-1].depth = depth;
2955                                 pvInfoList[forwardMostMove-1].score = 100*score;
2956                             }
2957                             OutputKibitz(suppressKibitz, parse);
2958                         } else {
2959                             char tmp[MSG_SIZ];
2960                             if(gameMode == IcsObserving) // restore original ICS messages
2961                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2962                             else
2963                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2964                             SendToPlayer(tmp, strlen(tmp));
2965                         }
2966                         next_out = i+1; // [HGM] suppress printing in ICS window
2967                     }
2968                     started = STARTED_NONE;
2969                 } else {
2970                     /* Don't match patterns against characters in comment */
2971                     i++;
2972                     continue;
2973                 }
2974             }
2975             if (started == STARTED_CHATTER) {
2976                 if (buf[i] != '\n') {
2977                     /* Don't match patterns against characters in chatter */
2978                     i++;
2979                     continue;
2980                 }
2981                 started = STARTED_NONE;
2982                 if(suppressKibitz) next_out = i+1;
2983             }
2984
2985             /* Kludge to deal with rcmd protocol */
2986             if (firstTime && looking_at(buf, &i, "\001*")) {
2987                 DisplayFatalError(&buf[1], 0, 1);
2988                 continue;
2989             } else {
2990                 firstTime = FALSE;
2991             }
2992
2993             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2994                 ics_type = ICS_ICC;
2995                 ics_prefix = "/";
2996                 if (appData.debugMode)
2997                   fprintf(debugFP, "ics_type %d\n", ics_type);
2998                 continue;
2999             }
3000             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3001                 ics_type = ICS_FICS;
3002                 ics_prefix = "$";
3003                 if (appData.debugMode)
3004                   fprintf(debugFP, "ics_type %d\n", ics_type);
3005                 continue;
3006             }
3007             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3008                 ics_type = ICS_CHESSNET;
3009                 ics_prefix = "/";
3010                 if (appData.debugMode)
3011                   fprintf(debugFP, "ics_type %d\n", ics_type);
3012                 continue;
3013             }
3014
3015             if (!loggedOn &&
3016                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3017                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3018                  looking_at(buf, &i, "will be \"*\""))) {
3019               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3020               continue;
3021             }
3022
3023             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3024               char buf[MSG_SIZ];
3025               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3026               DisplayIcsInteractionTitle(buf);
3027               have_set_title = TRUE;
3028             }
3029
3030             /* skip finger notes */
3031             if (started == STARTED_NONE &&
3032                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3033                  (buf[i] == '1' && buf[i+1] == '0')) &&
3034                 buf[i+2] == ':' && buf[i+3] == ' ') {
3035               started = STARTED_CHATTER;
3036               i += 3;
3037               continue;
3038             }
3039
3040             oldi = i;
3041             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3042             if(appData.seekGraph) {
3043                 if(soughtPending && MatchSoughtLine(buf+i)) {
3044                     i = strstr(buf+i, "rated") - buf;
3045                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3046                     next_out = leftover_start = i;
3047                     started = STARTED_CHATTER;
3048                     suppressKibitz = TRUE;
3049                     continue;
3050                 }
3051                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3052                         && looking_at(buf, &i, "* ads displayed")) {
3053                     soughtPending = FALSE;
3054                     seekGraphUp = TRUE;
3055                     DrawSeekGraph();
3056                     continue;
3057                 }
3058                 if(appData.autoRefresh) {
3059                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3060                         int s = (ics_type == ICS_ICC); // ICC format differs
3061                         if(seekGraphUp)
3062                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3063                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3064                         looking_at(buf, &i, "*% "); // eat prompt
3065                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3066                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3067                         next_out = i; // suppress
3068                         continue;
3069                     }
3070                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3071                         char *p = star_match[0];
3072                         while(*p) {
3073                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3074                             while(*p && *p++ != ' '); // next
3075                         }
3076                         looking_at(buf, &i, "*% "); // eat prompt
3077                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3078                         next_out = i;
3079                         continue;
3080                     }
3081                 }
3082             }
3083
3084             /* skip formula vars */
3085             if (started == STARTED_NONE &&
3086                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3087               started = STARTED_CHATTER;
3088               i += 3;
3089               continue;
3090             }
3091
3092             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3093             if (appData.autoKibitz && started == STARTED_NONE &&
3094                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3095                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3096                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3097                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3098                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3099                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3100                         suppressKibitz = TRUE;
3101                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3102                         next_out = i;
3103                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3104                                 && (gameMode == IcsPlayingWhite)) ||
3105                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3106                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3107                             started = STARTED_CHATTER; // own kibitz we simply discard
3108                         else {
3109                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3110                             parse_pos = 0; parse[0] = NULLCHAR;
3111                             savingComment = TRUE;
3112                             suppressKibitz = gameMode != IcsObserving ? 2 :
3113                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3114                         }
3115                         continue;
3116                 } else
3117                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3118                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3119                          && atoi(star_match[0])) {
3120                     // suppress the acknowledgements of our own autoKibitz
3121                     char *p;
3122                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3123                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3124                     SendToPlayer(star_match[0], strlen(star_match[0]));
3125                     if(looking_at(buf, &i, "*% ")) // eat prompt
3126                         suppressKibitz = FALSE;
3127                     next_out = i;
3128                     continue;
3129                 }
3130             } // [HGM] kibitz: end of patch
3131
3132             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3133
3134             // [HGM] chat: intercept tells by users for which we have an open chat window
3135             channel = -1;
3136             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3137                                            looking_at(buf, &i, "* whispers:") ||
3138                                            looking_at(buf, &i, "* kibitzes:") ||
3139                                            looking_at(buf, &i, "* shouts:") ||
3140                                            looking_at(buf, &i, "* c-shouts:") ||
3141                                            looking_at(buf, &i, "--> * ") ||
3142                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3143                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3144                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3145                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3146                 int p;
3147                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3148                 chattingPartner = -1;
3149
3150                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3151                 for(p=0; p<MAX_CHAT; p++) {
3152                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3153                     talker[0] = '['; strcat(talker, "] ");
3154                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3155                     chattingPartner = p; break;
3156                     }
3157                 } else
3158                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3159                 for(p=0; p<MAX_CHAT; p++) {
3160                     if(!strcmp("kibitzes", chatPartner[p])) {
3161                         talker[0] = '['; strcat(talker, "] ");
3162                         chattingPartner = p; break;
3163                     }
3164                 } else
3165                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3166                 for(p=0; p<MAX_CHAT; p++) {
3167                     if(!strcmp("whispers", chatPartner[p])) {
3168                         talker[0] = '['; strcat(talker, "] ");
3169                         chattingPartner = p; break;
3170                     }
3171                 } else
3172                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3173                   if(buf[i-8] == '-' && buf[i-3] == 't')
3174                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3175                     if(!strcmp("c-shouts", chatPartner[p])) {
3176                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3177                         chattingPartner = p; break;
3178                     }
3179                   }
3180                   if(chattingPartner < 0)
3181                   for(p=0; p<MAX_CHAT; p++) {
3182                     if(!strcmp("shouts", chatPartner[p])) {
3183                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3184                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3185                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3186                         chattingPartner = p; break;
3187                     }
3188                   }
3189                 }
3190                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3191                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3192                     talker[0] = 0; Colorize(ColorTell, FALSE);
3193                     chattingPartner = p; break;
3194                 }
3195                 if(chattingPartner<0) i = oldi; else {
3196                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3197                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3198                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3199                     started = STARTED_COMMENT;
3200                     parse_pos = 0; parse[0] = NULLCHAR;
3201                     savingComment = 3 + chattingPartner; // counts as TRUE
3202                     suppressKibitz = TRUE;
3203                     continue;
3204                 }
3205             } // [HGM] chat: end of patch
3206
3207           backup = i;
3208             if (appData.zippyTalk || appData.zippyPlay) {
3209                 /* [DM] Backup address for color zippy lines */
3210 #if ZIPPY
3211                if (loggedOn == TRUE)
3212                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3213                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3214 #endif
3215             } // [DM] 'else { ' deleted
3216                 if (
3217                     /* Regular tells and says */
3218                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3219                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3220                     looking_at(buf, &i, "* says: ") ||
3221                     /* Don't color "message" or "messages" output */
3222                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3223                     looking_at(buf, &i, "*. * at *:*: ") ||
3224                     looking_at(buf, &i, "--* (*:*): ") ||
3225                     /* Message notifications (same color as tells) */
3226                     looking_at(buf, &i, "* has left a message ") ||
3227                     looking_at(buf, &i, "* just sent you a message:\n") ||
3228                     /* Whispers and kibitzes */
3229                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3230                     looking_at(buf, &i, "* kibitzes: ") ||
3231                     /* Channel tells */
3232                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3233
3234                   if (tkind == 1 && strchr(star_match[0], ':')) {
3235                       /* Avoid "tells you:" spoofs in channels */
3236                      tkind = 3;
3237                   }
3238                   if (star_match[0][0] == NULLCHAR ||
3239                       strchr(star_match[0], ' ') ||
3240                       (tkind == 3 && strchr(star_match[1], ' '))) {
3241                     /* Reject bogus matches */
3242                     i = oldi;
3243                   } else {
3244                     if (appData.colorize) {
3245                       if (oldi > next_out) {
3246                         SendToPlayer(&buf[next_out], oldi - next_out);
3247                         next_out = oldi;
3248                       }
3249                       switch (tkind) {
3250                       case 1:
3251                         Colorize(ColorTell, FALSE);
3252                         curColor = ColorTell;
3253                         break;
3254                       case 2:
3255                         Colorize(ColorKibitz, FALSE);
3256                         curColor = ColorKibitz;
3257                         break;
3258                       case 3:
3259                         p = strrchr(star_match[1], '(');
3260                         if (p == NULL) {
3261                           p = star_match[1];
3262                         } else {
3263                           p++;
3264                         }
3265                         if (atoi(p) == 1) {
3266                           Colorize(ColorChannel1, FALSE);
3267                           curColor = ColorChannel1;
3268                         } else {
3269                           Colorize(ColorChannel, FALSE);
3270                           curColor = ColorChannel;
3271                         }
3272                         break;
3273                       case 5:
3274                         curColor = ColorNormal;
3275                         break;
3276                       }
3277                     }
3278                     if (started == STARTED_NONE && appData.autoComment &&
3279                         (gameMode == IcsObserving ||
3280                          gameMode == IcsPlayingWhite ||
3281                          gameMode == IcsPlayingBlack)) {
3282                       parse_pos = i - oldi;
3283                       memcpy(parse, &buf[oldi], parse_pos);
3284                       parse[parse_pos] = NULLCHAR;
3285                       started = STARTED_COMMENT;
3286                       savingComment = TRUE;
3287                     } else {
3288                       started = STARTED_CHATTER;
3289                       savingComment = FALSE;
3290                     }
3291                     loggedOn = TRUE;
3292                     continue;
3293                   }
3294                 }
3295
3296                 if (looking_at(buf, &i, "* s-shouts: ") ||
3297                     looking_at(buf, &i, "* c-shouts: ")) {
3298                     if (appData.colorize) {
3299                         if (oldi > next_out) {
3300                             SendToPlayer(&buf[next_out], oldi - next_out);
3301                             next_out = oldi;
3302                         }
3303                         Colorize(ColorSShout, FALSE);
3304                         curColor = ColorSShout;
3305                     }
3306                     loggedOn = TRUE;
3307                     started = STARTED_CHATTER;
3308                     continue;
3309                 }
3310
3311                 if (looking_at(buf, &i, "--->")) {
3312                     loggedOn = TRUE;
3313                     continue;
3314                 }
3315
3316                 if (looking_at(buf, &i, "* shouts: ") ||
3317                     looking_at(buf, &i, "--> ")) {
3318                     if (appData.colorize) {
3319                         if (oldi > next_out) {
3320                             SendToPlayer(&buf[next_out], oldi - next_out);
3321                             next_out = oldi;
3322                         }
3323                         Colorize(ColorShout, FALSE);
3324                         curColor = ColorShout;
3325                     }
3326                     loggedOn = TRUE;
3327                     started = STARTED_CHATTER;
3328                     continue;
3329                 }
3330
3331                 if (looking_at( buf, &i, "Challenge:")) {
3332                     if (appData.colorize) {
3333                         if (oldi > next_out) {
3334                             SendToPlayer(&buf[next_out], oldi - next_out);
3335                             next_out = oldi;
3336                         }
3337                         Colorize(ColorChallenge, FALSE);
3338                         curColor = ColorChallenge;
3339                     }
3340                     loggedOn = TRUE;
3341                     continue;
3342                 }
3343
3344                 if (looking_at(buf, &i, "* offers you") ||
3345                     looking_at(buf, &i, "* offers to be") ||
3346                     looking_at(buf, &i, "* would like to") ||
3347                     looking_at(buf, &i, "* requests to") ||
3348                     looking_at(buf, &i, "Your opponent offers") ||
3349                     looking_at(buf, &i, "Your opponent requests")) {
3350
3351                     if (appData.colorize) {
3352                         if (oldi > next_out) {
3353                             SendToPlayer(&buf[next_out], oldi - next_out);
3354                             next_out = oldi;
3355                         }
3356                         Colorize(ColorRequest, FALSE);
3357                         curColor = ColorRequest;
3358                     }
3359                     continue;
3360                 }
3361
3362                 if (looking_at(buf, &i, "* (*) seeking")) {
3363                     if (appData.colorize) {
3364                         if (oldi > next_out) {
3365                             SendToPlayer(&buf[next_out], oldi - next_out);
3366                             next_out = oldi;
3367                         }
3368                         Colorize(ColorSeek, FALSE);
3369                         curColor = ColorSeek;
3370                     }
3371                     continue;
3372             }
3373
3374           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3375
3376             if (looking_at(buf, &i, "\\   ")) {
3377                 if (prevColor != ColorNormal) {
3378                     if (oldi > next_out) {
3379                         SendToPlayer(&buf[next_out], oldi - next_out);
3380                         next_out = oldi;
3381                     }
3382                     Colorize(prevColor, TRUE);
3383                     curColor = prevColor;
3384                 }
3385                 if (savingComment) {
3386                     parse_pos = i - oldi;
3387                     memcpy(parse, &buf[oldi], parse_pos);
3388                     parse[parse_pos] = NULLCHAR;
3389                     started = STARTED_COMMENT;
3390                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3391                         chattingPartner = savingComment - 3; // kludge to remember the box
3392                 } else {
3393                     started = STARTED_CHATTER;
3394                 }
3395                 continue;
3396             }
3397
3398             if (looking_at(buf, &i, "Black Strength :") ||
3399                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3400                 looking_at(buf, &i, "<10>") ||
3401                 looking_at(buf, &i, "#@#")) {
3402                 /* Wrong board style */
3403                 loggedOn = TRUE;
3404                 SendToICS(ics_prefix);
3405                 SendToICS("set style 12\n");
3406                 SendToICS(ics_prefix);
3407                 SendToICS("refresh\n");
3408                 continue;
3409             }
3410
3411             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3412                 ICSInitScript();
3413                 have_sent_ICS_logon = 1;
3414                 continue;
3415             }
3416
3417             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3418                 (looking_at(buf, &i, "\n<12> ") ||
3419                  looking_at(buf, &i, "<12> "))) {
3420                 loggedOn = TRUE;
3421                 if (oldi > next_out) {
3422                     SendToPlayer(&buf[next_out], oldi - next_out);
3423                 }
3424                 next_out = i;
3425                 started = STARTED_BOARD;
3426                 parse_pos = 0;
3427                 continue;
3428             }
3429
3430             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3431                 looking_at(buf, &i, "<b1> ")) {
3432                 if (oldi > next_out) {
3433                     SendToPlayer(&buf[next_out], oldi - next_out);
3434                 }
3435                 next_out = i;
3436                 started = STARTED_HOLDINGS;
3437                 parse_pos = 0;
3438                 continue;
3439             }
3440
3441             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3442                 loggedOn = TRUE;
3443                 /* Header for a move list -- first line */
3444
3445                 switch (ics_getting_history) {
3446                   case H_FALSE:
3447                     switch (gameMode) {
3448                       case IcsIdle:
3449                       case BeginningOfGame:
3450                         /* User typed "moves" or "oldmoves" while we
3451                            were idle.  Pretend we asked for these
3452                            moves and soak them up so user can step
3453                            through them and/or save them.
3454                            */
3455                         Reset(FALSE, TRUE);
3456                         gameMode = IcsObserving;
3457                         ModeHighlight();
3458                         ics_gamenum = -1;
3459                         ics_getting_history = H_GOT_UNREQ_HEADER;
3460                         break;
3461                       case EditGame: /*?*/
3462                       case EditPosition: /*?*/
3463                         /* Should above feature work in these modes too? */
3464                         /* For now it doesn't */
3465                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3466                         break;
3467                       default:
3468                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3469                         break;
3470                     }
3471                     break;
3472                   case H_REQUESTED:
3473                     /* Is this the right one? */
3474                     if (gameInfo.white && gameInfo.black &&
3475                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3476                         strcmp(gameInfo.black, star_match[2]) == 0) {
3477                         /* All is well */
3478                         ics_getting_history = H_GOT_REQ_HEADER;
3479                     }
3480                     break;
3481                   case H_GOT_REQ_HEADER:
3482                   case H_GOT_UNREQ_HEADER:
3483                   case H_GOT_UNWANTED_HEADER:
3484                   case H_GETTING_MOVES:
3485                     /* Should not happen */
3486                     DisplayError(_("Error gathering move list: two headers"), 0);
3487                     ics_getting_history = H_FALSE;
3488                     break;
3489                 }
3490
3491                 /* Save player ratings into gameInfo if needed */
3492                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3493                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3494                     (gameInfo.whiteRating == -1 ||
3495                      gameInfo.blackRating == -1)) {
3496
3497                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3498                     gameInfo.blackRating = string_to_rating(star_match[3]);
3499                     if (appData.debugMode)
3500                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3501                               gameInfo.whiteRating, gameInfo.blackRating);
3502                 }
3503                 continue;
3504             }
3505
3506             if (looking_at(buf, &i,
3507               "* * match, initial time: * minute*, increment: * second")) {
3508                 /* Header for a move list -- second line */
3509                 /* Initial board will follow if this is a wild game */
3510                 if (gameInfo.event != NULL) free(gameInfo.event);
3511                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3512                 gameInfo.event = StrSave(str);
3513                 /* [HGM] we switched variant. Translate boards if needed. */
3514                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3515                 continue;
3516             }
3517
3518             if (looking_at(buf, &i, "Move  ")) {
3519                 /* Beginning of a move list */
3520                 switch (ics_getting_history) {
3521                   case H_FALSE:
3522                     /* Normally should not happen */
3523                     /* Maybe user hit reset while we were parsing */
3524                     break;
3525                   case H_REQUESTED:
3526                     /* Happens if we are ignoring a move list that is not
3527                      * the one we just requested.  Common if the user
3528                      * tries to observe two games without turning off
3529                      * getMoveList */
3530                     break;
3531                   case H_GETTING_MOVES:
3532                     /* Should not happen */
3533                     DisplayError(_("Error gathering move list: nested"), 0);
3534                     ics_getting_history = H_FALSE;
3535                     break;
3536                   case H_GOT_REQ_HEADER:
3537                     ics_getting_history = H_GETTING_MOVES;
3538                     started = STARTED_MOVES;
3539                     parse_pos = 0;
3540                     if (oldi > next_out) {
3541                         SendToPlayer(&buf[next_out], oldi - next_out);
3542                     }
3543                     break;
3544                   case H_GOT_UNREQ_HEADER:
3545                     ics_getting_history = H_GETTING_MOVES;
3546                     started = STARTED_MOVES_NOHIDE;
3547                     parse_pos = 0;
3548                     break;
3549                   case H_GOT_UNWANTED_HEADER:
3550                     ics_getting_history = H_FALSE;
3551                     break;
3552                 }
3553                 continue;
3554             }
3555
3556             if (looking_at(buf, &i, "% ") ||
3557                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3558                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3559                 if(soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3560                     soughtPending = FALSE;
3561                     seekGraphUp = TRUE;
3562                     DrawSeekGraph();
3563                 }
3564                 if(suppressKibitz) next_out = i;
3565                 savingComment = FALSE;
3566                 suppressKibitz = 0;
3567                 switch (started) {
3568                   case STARTED_MOVES:
3569                   case STARTED_MOVES_NOHIDE:
3570                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3571                     parse[parse_pos + i - oldi] = NULLCHAR;
3572                     ParseGameHistory(parse);
3573 #if ZIPPY
3574                     if (appData.zippyPlay && first.initDone) {
3575                         FeedMovesToProgram(&first, forwardMostMove);
3576                         if (gameMode == IcsPlayingWhite) {
3577                             if (WhiteOnMove(forwardMostMove)) {
3578                                 if (first.sendTime) {
3579                                   if (first.useColors) {
3580                                     SendToProgram("black\n", &first);
3581                                   }
3582                                   SendTimeRemaining(&first, TRUE);
3583                                 }
3584                                 if (first.useColors) {
3585                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3586                                 }
3587                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3588                                 first.maybeThinking = TRUE;
3589                             } else {
3590                                 if (first.usePlayother) {
3591                                   if (first.sendTime) {
3592                                     SendTimeRemaining(&first, TRUE);
3593                                   }
3594                                   SendToProgram("playother\n", &first);
3595                                   firstMove = FALSE;
3596                                 } else {
3597                                   firstMove = TRUE;
3598                                 }
3599                             }
3600                         } else if (gameMode == IcsPlayingBlack) {
3601                             if (!WhiteOnMove(forwardMostMove)) {
3602                                 if (first.sendTime) {
3603                                   if (first.useColors) {
3604                                     SendToProgram("white\n", &first);
3605                                   }
3606                                   SendTimeRemaining(&first, FALSE);
3607                                 }
3608                                 if (first.useColors) {
3609                                   SendToProgram("black\n", &first);
3610                                 }
3611                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3612                                 first.maybeThinking = TRUE;
3613                             } else {
3614                                 if (first.usePlayother) {
3615                                   if (first.sendTime) {
3616                                     SendTimeRemaining(&first, FALSE);
3617                                   }
3618                                   SendToProgram("playother\n", &first);
3619                                   firstMove = FALSE;
3620                                 } else {
3621                                   firstMove = TRUE;
3622                                 }
3623                             }
3624                         }
3625                     }
3626 #endif
3627                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3628                         /* Moves came from oldmoves or moves command
3629                            while we weren't doing anything else.
3630                            */
3631                         currentMove = forwardMostMove;
3632                         ClearHighlights();/*!!could figure this out*/
3633                         flipView = appData.flipView;
3634                         DrawPosition(TRUE, boards[currentMove]);
3635                         DisplayBothClocks();
3636                         snprintf(str, MSG_SIZ, "%s %s %s",
3637                                 gameInfo.white, _("vs."),  gameInfo.black);
3638                         DisplayTitle(str);
3639                         gameMode = IcsIdle;
3640                     } else {
3641                         /* Moves were history of an active game */
3642                         if (gameInfo.resultDetails != NULL) {
3643                             free(gameInfo.resultDetails);
3644                             gameInfo.resultDetails = NULL;
3645                         }
3646                     }
3647                     HistorySet(parseList, backwardMostMove,
3648                                forwardMostMove, currentMove-1);
3649                     DisplayMove(currentMove - 1);
3650                     if (started == STARTED_MOVES) next_out = i;
3651                     started = STARTED_NONE;
3652                     ics_getting_history = H_FALSE;
3653                     break;
3654
3655                   case STARTED_OBSERVE:
3656                     started = STARTED_NONE;
3657                     SendToICS(ics_prefix);
3658                     SendToICS("refresh\n");
3659                     break;
3660
3661                   default:
3662                     break;
3663                 }
3664                 if(bookHit) { // [HGM] book: simulate book reply
3665                     static char bookMove[MSG_SIZ]; // a bit generous?
3666
3667                     programStats.nodes = programStats.depth = programStats.time =
3668                     programStats.score = programStats.got_only_move = 0;
3669                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3670
3671                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3672                     strcat(bookMove, bookHit);
3673                     HandleMachineMove(bookMove, &first);
3674                 }
3675                 continue;
3676             }
3677
3678             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3679                  started == STARTED_HOLDINGS ||
3680                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3681                 /* Accumulate characters in move list or board */
3682                 parse[parse_pos++] = buf[i];
3683             }
3684
3685             /* Start of game messages.  Mostly we detect start of game
3686                when the first board image arrives.  On some versions
3687                of the ICS, though, we need to do a "refresh" after starting
3688                to observe in order to get the current board right away. */
3689             if (looking_at(buf, &i, "Adding game * to observation list")) {
3690                 started = STARTED_OBSERVE;
3691                 continue;
3692             }
3693
3694             /* Handle auto-observe */
3695             if (appData.autoObserve &&
3696                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3697                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3698                 char *player;
3699                 /* Choose the player that was highlighted, if any. */
3700                 if (star_match[0][0] == '\033' ||
3701                     star_match[1][0] != '\033') {
3702                     player = star_match[0];
3703                 } else {
3704                     player = star_match[2];
3705                 }
3706                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3707                         ics_prefix, StripHighlightAndTitle(player));
3708                 SendToICS(str);
3709
3710                 /* Save ratings from notify string */
3711                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3712                 player1Rating = string_to_rating(star_match[1]);
3713                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3714                 player2Rating = string_to_rating(star_match[3]);
3715
3716                 if (appData.debugMode)
3717                   fprintf(debugFP,
3718                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3719                           player1Name, player1Rating,
3720                           player2Name, player2Rating);
3721
3722                 continue;
3723             }
3724
3725             /* Deal with automatic examine mode after a game,
3726                and with IcsObserving -> IcsExamining transition */
3727             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3728                 looking_at(buf, &i, "has made you an examiner of game *")) {
3729
3730                 int gamenum = atoi(star_match[0]);
3731                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3732                     gamenum == ics_gamenum) {
3733                     /* We were already playing or observing this game;
3734                        no need to refetch history */
3735                     gameMode = IcsExamining;
3736                     if (pausing) {
3737                         pauseExamForwardMostMove = forwardMostMove;
3738                     } else if (currentMove < forwardMostMove) {
3739                         ForwardInner(forwardMostMove);
3740                     }
3741                 } else {
3742                     /* I don't think this case really can happen */
3743                     SendToICS(ics_prefix);
3744                     SendToICS("refresh\n");
3745                 }
3746                 continue;
3747             }
3748
3749             /* Error messages */
3750 //          if (ics_user_moved) {
3751             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3752                 if (looking_at(buf, &i, "Illegal move") ||
3753                     looking_at(buf, &i, "Not a legal move") ||
3754                     looking_at(buf, &i, "Your king is in check") ||
3755                     looking_at(buf, &i, "It isn't your turn") ||
3756                     looking_at(buf, &i, "It is not your move")) {
3757                     /* Illegal move */
3758                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3759                         currentMove = forwardMostMove-1;
3760                         DisplayMove(currentMove - 1); /* before DMError */
3761                         DrawPosition(FALSE, boards[currentMove]);
3762                         SwitchClocks(forwardMostMove-1); // [HGM] race
3763                         DisplayBothClocks();
3764                     }
3765                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3766                     ics_user_moved = 0;
3767                     continue;
3768                 }
3769             }
3770
3771             if (looking_at(buf, &i, "still have time") ||
3772                 looking_at(buf, &i, "not out of time") ||
3773                 looking_at(buf, &i, "either player is out of time") ||
3774                 looking_at(buf, &i, "has timeseal; checking")) {
3775                 /* We must have called his flag a little too soon */
3776                 whiteFlag = blackFlag = FALSE;
3777                 continue;
3778             }
3779
3780             if (looking_at(buf, &i, "added * seconds to") ||
3781                 looking_at(buf, &i, "seconds were added to")) {
3782                 /* Update the clocks */
3783                 SendToICS(ics_prefix);
3784                 SendToICS("refresh\n");
3785                 continue;
3786             }
3787
3788             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3789                 ics_clock_paused = TRUE;
3790                 StopClocks();
3791                 continue;
3792             }
3793
3794             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3795                 ics_clock_paused = FALSE;
3796                 StartClocks();
3797                 continue;
3798             }
3799
3800             /* Grab player ratings from the Creating: message.
3801                Note we have to check for the special case when
3802                the ICS inserts things like [white] or [black]. */
3803             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3804                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3805                 /* star_matches:
3806                    0    player 1 name (not necessarily white)
3807                    1    player 1 rating
3808                    2    empty, white, or black (IGNORED)
3809                    3    player 2 name (not necessarily black)
3810                    4    player 2 rating
3811
3812                    The names/ratings are sorted out when the game
3813                    actually starts (below).
3814                 */
3815                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3816                 player1Rating = string_to_rating(star_match[1]);
3817                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3818                 player2Rating = string_to_rating(star_match[4]);
3819
3820                 if (appData.debugMode)
3821                   fprintf(debugFP,
3822                           "Ratings from 'Creating:' %s %d, %s %d\n",
3823                           player1Name, player1Rating,
3824                           player2Name, player2Rating);
3825
3826                 continue;
3827             }
3828
3829             /* Improved generic start/end-of-game messages */
3830             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3831                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3832                 /* If tkind == 0: */
3833                 /* star_match[0] is the game number */
3834                 /*           [1] is the white player's name */
3835                 /*           [2] is the black player's name */
3836                 /* For end-of-game: */
3837                 /*           [3] is the reason for the game end */
3838                 /*           [4] is a PGN end game-token, preceded by " " */
3839                 /* For start-of-game: */
3840                 /*           [3] begins with "Creating" or "Continuing" */
3841                 /*           [4] is " *" or empty (don't care). */
3842                 int gamenum = atoi(star_match[0]);
3843                 char *whitename, *blackname, *why, *endtoken;
3844                 ChessMove endtype = EndOfFile;
3845
3846                 if (tkind == 0) {
3847                   whitename = star_match[1];
3848                   blackname = star_match[2];
3849                   why = star_match[3];
3850                   endtoken = star_match[4];
3851                 } else {
3852                   whitename = star_match[1];
3853                   blackname = star_match[3];
3854                   why = star_match[5];
3855                   endtoken = star_match[6];
3856                 }
3857
3858                 /* Game start messages */
3859                 if (strncmp(why, "Creating ", 9) == 0 ||
3860                     strncmp(why, "Continuing ", 11) == 0) {
3861                     gs_gamenum = gamenum;
3862                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3863                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3864                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3865 #if ZIPPY
3866                     if (appData.zippyPlay) {
3867                         ZippyGameStart(whitename, blackname);
3868                     }
3869 #endif /*ZIPPY*/
3870                     partnerBoardValid = FALSE; // [HGM] bughouse
3871                     continue;
3872                 }
3873
3874                 /* Game end messages */
3875                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3876                     ics_gamenum != gamenum) {
3877                     continue;
3878                 }
3879                 while (endtoken[0] == ' ') endtoken++;
3880                 switch (endtoken[0]) {
3881                   case '*':
3882                   default:
3883                     endtype = GameUnfinished;
3884                     break;
3885                   case '0':
3886                     endtype = BlackWins;
3887                     break;
3888                   case '1':
3889                     if (endtoken[1] == '/')
3890                       endtype = GameIsDrawn;
3891                     else
3892                       endtype = WhiteWins;
3893                     break;
3894                 }
3895                 GameEnds(endtype, why, GE_ICS);
3896 #if ZIPPY
3897                 if (appData.zippyPlay && first.initDone) {
3898                     ZippyGameEnd(endtype, why);
3899                     if (first.pr == NoProc) {
3900                       /* Start the next process early so that we'll
3901                          be ready for the next challenge */
3902                       StartChessProgram(&first);
3903                     }
3904                     /* Send "new" early, in case this command takes
3905                        a long time to finish, so that we'll be ready
3906                        for the next challenge. */
3907                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3908                     Reset(TRUE, TRUE);
3909                 }
3910 #endif /*ZIPPY*/
3911                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3912                 continue;
3913             }
3914
3915             if (looking_at(buf, &i, "Removing game * from observation") ||
3916                 looking_at(buf, &i, "no longer observing game *") ||
3917                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3918                 if (gameMode == IcsObserving &&
3919                     atoi(star_match[0]) == ics_gamenum)
3920                   {
3921                       /* icsEngineAnalyze */
3922                       if (appData.icsEngineAnalyze) {
3923                             ExitAnalyzeMode();
3924                             ModeHighlight();
3925                       }
3926                       StopClocks();
3927                       gameMode = IcsIdle;
3928                       ics_gamenum = -1;
3929                       ics_user_moved = FALSE;
3930                   }
3931                 continue;
3932             }
3933
3934             if (looking_at(buf, &i, "no longer examining game *")) {
3935                 if (gameMode == IcsExamining &&
3936                     atoi(star_match[0]) == ics_gamenum)
3937                   {
3938                       gameMode = IcsIdle;
3939                       ics_gamenum = -1;
3940                       ics_user_moved = FALSE;
3941                   }
3942                 continue;
3943             }
3944
3945             /* Advance leftover_start past any newlines we find,
3946                so only partial lines can get reparsed */
3947             if (looking_at(buf, &i, "\n")) {
3948                 prevColor = curColor;
3949                 if (curColor != ColorNormal) {
3950                     if (oldi > next_out) {
3951                         SendToPlayer(&buf[next_out], oldi - next_out);
3952                         next_out = oldi;
3953                     }
3954                     Colorize(ColorNormal, FALSE);
3955                     curColor = ColorNormal;
3956                 }
3957                 if (started == STARTED_BOARD) {
3958                     started = STARTED_NONE;
3959                     parse[parse_pos] = NULLCHAR;
3960                     ParseBoard12(parse);
3961                     ics_user_moved = 0;
3962
3963                     /* Send premove here */
3964                     if (appData.premove) {
3965                       char str[MSG_SIZ];
3966                       if (currentMove == 0 &&
3967                           gameMode == IcsPlayingWhite &&
3968                           appData.premoveWhite) {
3969                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3970                         if (appData.debugMode)
3971                           fprintf(debugFP, "Sending premove:\n");
3972                         SendToICS(str);
3973                       } else if (currentMove == 1 &&
3974                                  gameMode == IcsPlayingBlack &&
3975                                  appData.premoveBlack) {
3976                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3977                         if (appData.debugMode)
3978                           fprintf(debugFP, "Sending premove:\n");
3979                         SendToICS(str);
3980                       } else if (gotPremove) {
3981                         gotPremove = 0;
3982                         ClearPremoveHighlights();
3983                         if (appData.debugMode)
3984                           fprintf(debugFP, "Sending premove:\n");
3985                           UserMoveEvent(premoveFromX, premoveFromY,
3986                                         premoveToX, premoveToY,
3987                                         premovePromoChar);
3988                       }
3989                     }
3990
3991                     /* Usually suppress following prompt */
3992                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3993                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3994                         if (looking_at(buf, &i, "*% ")) {
3995                             savingComment = FALSE;
3996                             suppressKibitz = 0;
3997                         }
3998                     }
3999                     next_out = i;
4000                 } else if (started == STARTED_HOLDINGS) {
4001                     int gamenum;
4002                     char new_piece[MSG_SIZ];
4003                     started = STARTED_NONE;
4004                     parse[parse_pos] = NULLCHAR;
4005                     if (appData.debugMode)
4006                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4007                                                         parse, currentMove);
4008                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4009                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4010                         if (gameInfo.variant == VariantNormal) {
4011                           /* [HGM] We seem to switch variant during a game!
4012                            * Presumably no holdings were displayed, so we have
4013                            * to move the position two files to the right to
4014                            * create room for them!
4015                            */
4016                           VariantClass newVariant;
4017                           switch(gameInfo.boardWidth) { // base guess on board width
4018                                 case 9:  newVariant = VariantShogi; break;
4019                                 case 10: newVariant = VariantGreat; break;
4020                                 default: newVariant = VariantCrazyhouse; break;
4021                           }
4022                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4023                           /* Get a move list just to see the header, which
4024                              will tell us whether this is really bug or zh */
4025                           if (ics_getting_history == H_FALSE) {
4026                             ics_getting_history = H_REQUESTED;
4027                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4028                             SendToICS(str);
4029                           }
4030                         }
4031                         new_piece[0] = NULLCHAR;
4032                         sscanf(parse, "game %d white [%s black [%s <- %s",
4033                                &gamenum, white_holding, black_holding,
4034                                new_piece);
4035                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4036                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4037                         /* [HGM] copy holdings to board holdings area */
4038                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4039                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4040                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4041 #if ZIPPY
4042                         if (appData.zippyPlay && first.initDone) {
4043                             ZippyHoldings(white_holding, black_holding,
4044                                           new_piece);
4045                         }
4046 #endif /*ZIPPY*/
4047                         if (tinyLayout || smallLayout) {
4048                             char wh[16], bh[16];
4049                             PackHolding(wh, white_holding);
4050                             PackHolding(bh, black_holding);
4051                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4052                                     gameInfo.white, gameInfo.black);
4053                         } else {
4054                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4055                                     gameInfo.white, white_holding, _("vs."),
4056                                     gameInfo.black, black_holding);
4057                         }
4058                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4059                         DrawPosition(FALSE, boards[currentMove]);
4060                         DisplayTitle(str);
4061                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4062                         sscanf(parse, "game %d white [%s black [%s <- %s",
4063                                &gamenum, white_holding, black_holding,
4064                                new_piece);
4065                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4066                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4067                         /* [HGM] copy holdings to partner-board holdings area */
4068                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4069                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4070                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4071                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4072                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4073                       }
4074                     }
4075                     /* Suppress following prompt */
4076                     if (looking_at(buf, &i, "*% ")) {
4077                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4078                         savingComment = FALSE;
4079                         suppressKibitz = 0;
4080                     }
4081                     next_out = i;
4082                 }
4083                 continue;
4084             }
4085
4086             i++;                /* skip unparsed character and loop back */
4087         }
4088
4089         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4090 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4091 //          SendToPlayer(&buf[next_out], i - next_out);
4092             started != STARTED_HOLDINGS && leftover_start > next_out) {
4093             SendToPlayer(&buf[next_out], leftover_start - next_out);
4094             next_out = i;
4095         }
4096
4097         leftover_len = buf_len - leftover_start;
4098         /* if buffer ends with something we couldn't parse,
4099            reparse it after appending the next read */
4100
4101     } else if (count == 0) {
4102         RemoveInputSource(isr);
4103         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4104     } else {
4105         DisplayFatalError(_("Error reading from ICS"), error, 1);
4106     }
4107 }
4108
4109
4110 /* Board style 12 looks like this:
4111
4112    <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
4113
4114  * The "<12> " is stripped before it gets to this routine.  The two
4115  * trailing 0's (flip state and clock ticking) are later addition, and
4116  * some chess servers may not have them, or may have only the first.
4117  * Additional trailing fields may be added in the future.
4118  */
4119
4120 #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"
4121
4122 #define RELATION_OBSERVING_PLAYED    0
4123 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4124 #define RELATION_PLAYING_MYMOVE      1
4125 #define RELATION_PLAYING_NOTMYMOVE  -1
4126 #define RELATION_EXAMINING           2
4127 #define RELATION_ISOLATED_BOARD     -3
4128 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4129
4130 void
4131 ParseBoard12 (char *string)
4132 {
4133     GameMode newGameMode;
4134     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4135     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4136     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4137     char to_play, board_chars[200];
4138     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4139     char black[32], white[32];
4140     Board board;
4141     int prevMove = currentMove;
4142     int ticking = 2;
4143     ChessMove moveType;
4144     int fromX, fromY, toX, toY;
4145     char promoChar;
4146     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4147     char *bookHit = NULL; // [HGM] book
4148     Boolean weird = FALSE, reqFlag = FALSE;
4149
4150     fromX = fromY = toX = toY = -1;
4151
4152     newGame = FALSE;
4153
4154     if (appData.debugMode)
4155       fprintf(debugFP, _("Parsing board: %s\n"), string);
4156
4157     move_str[0] = NULLCHAR;
4158     elapsed_time[0] = NULLCHAR;
4159     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4160         int  i = 0, j;
4161         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4162             if(string[i] == ' ') { ranks++; files = 0; }
4163             else files++;
4164             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4165             i++;
4166         }
4167         for(j = 0; j <i; j++) board_chars[j] = string[j];
4168         board_chars[i] = '\0';
4169         string += i + 1;
4170     }
4171     n = sscanf(string, PATTERN, &to_play, &double_push,
4172                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4173                &gamenum, white, black, &relation, &basetime, &increment,
4174                &white_stren, &black_stren, &white_time, &black_time,
4175                &moveNum, str, elapsed_time, move_str, &ics_flip,
4176                &ticking);
4177
4178     if (n < 21) {
4179         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4180         DisplayError(str, 0);
4181         return;
4182     }
4183
4184     /* Convert the move number to internal form */
4185     moveNum = (moveNum - 1) * 2;
4186     if (to_play == 'B') moveNum++;
4187     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4188       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4189                         0, 1);
4190       return;
4191     }
4192
4193     switch (relation) {
4194       case RELATION_OBSERVING_PLAYED:
4195       case RELATION_OBSERVING_STATIC:
4196         if (gamenum == -1) {
4197             /* Old ICC buglet */
4198             relation = RELATION_OBSERVING_STATIC;
4199         }
4200         newGameMode = IcsObserving;
4201         break;
4202       case RELATION_PLAYING_MYMOVE:
4203       case RELATION_PLAYING_NOTMYMOVE:
4204         newGameMode =
4205           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4206             IcsPlayingWhite : IcsPlayingBlack;
4207         break;
4208       case RELATION_EXAMINING:
4209         newGameMode = IcsExamining;
4210         break;
4211       case RELATION_ISOLATED_BOARD:
4212       default:
4213         /* Just display this board.  If user was doing something else,
4214            we will forget about it until the next board comes. */
4215         newGameMode = IcsIdle;
4216         break;
4217       case RELATION_STARTING_POSITION:
4218         newGameMode = gameMode;
4219         break;
4220     }
4221
4222     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4223          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4224       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4225       char *toSqr;
4226       for (k = 0; k < ranks; k++) {
4227         for (j = 0; j < files; j++)
4228           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4229         if(gameInfo.holdingsWidth > 1) {
4230              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4231              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4232         }
4233       }
4234       CopyBoard(partnerBoard, board);
4235       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4236         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4237         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4238       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4239       if(toSqr = strchr(str, '-')) {
4240         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4241         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4242       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4243       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4244       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4245       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4246       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4247       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4248                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4249       DisplayMessage(partnerStatus, "");
4250         partnerBoardValid = TRUE;
4251       return;
4252     }
4253
4254     /* Modify behavior for initial board display on move listing
4255        of wild games.
4256        */
4257     switch (ics_getting_history) {
4258       case H_FALSE:
4259       case H_REQUESTED:
4260         break;
4261       case H_GOT_REQ_HEADER:
4262       case H_GOT_UNREQ_HEADER:
4263         /* This is the initial position of the current game */
4264         gamenum = ics_gamenum;
4265         moveNum = 0;            /* old ICS bug workaround */
4266         if (to_play == 'B') {
4267           startedFromSetupPosition = TRUE;
4268           blackPlaysFirst = TRUE;
4269           moveNum = 1;
4270           if (forwardMostMove == 0) forwardMostMove = 1;
4271           if (backwardMostMove == 0) backwardMostMove = 1;
4272           if (currentMove == 0) currentMove = 1;
4273         }
4274         newGameMode = gameMode;
4275         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4276         break;
4277       case H_GOT_UNWANTED_HEADER:
4278         /* This is an initial board that we don't want */
4279         return;
4280       case H_GETTING_MOVES:
4281         /* Should not happen */
4282         DisplayError(_("Error gathering move list: extra board"), 0);
4283         ics_getting_history = H_FALSE;
4284         return;
4285     }
4286
4287    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4288                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4289      /* [HGM] We seem to have switched variant unexpectedly
4290       * Try to guess new variant from board size
4291       */
4292           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4293           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4294           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4295           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4296           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4297           if(!weird) newVariant = VariantNormal;
4298           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4299           /* Get a move list just to see the header, which
4300              will tell us whether this is really bug or zh */
4301           if (ics_getting_history == H_FALSE) {
4302             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4303             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4304             SendToICS(str);
4305           }
4306     }
4307
4308     /* Take action if this is the first board of a new game, or of a
4309        different game than is currently being displayed.  */
4310     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4311         relation == RELATION_ISOLATED_BOARD) {
4312
4313         /* Forget the old game and get the history (if any) of the new one */
4314         if (gameMode != BeginningOfGame) {
4315           Reset(TRUE, TRUE);
4316         }
4317         newGame = TRUE;
4318         if (appData.autoRaiseBoard) BoardToTop();
4319         prevMove = -3;
4320         if (gamenum == -1) {
4321             newGameMode = IcsIdle;
4322         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4323                    appData.getMoveList && !reqFlag) {
4324             /* Need to get game history */
4325             ics_getting_history = H_REQUESTED;
4326             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4327             SendToICS(str);
4328         }
4329
4330         /* Initially flip the board to have black on the bottom if playing
4331            black or if the ICS flip flag is set, but let the user change
4332            it with the Flip View button. */
4333         flipView = appData.autoFlipView ?
4334           (newGameMode == IcsPlayingBlack) || ics_flip :
4335           appData.flipView;
4336
4337         /* Done with values from previous mode; copy in new ones */
4338         gameMode = newGameMode;
4339         ModeHighlight();
4340         ics_gamenum = gamenum;
4341         if (gamenum == gs_gamenum) {
4342             int klen = strlen(gs_kind);
4343             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4344             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4345             gameInfo.event = StrSave(str);
4346         } else {
4347             gameInfo.event = StrSave("ICS game");
4348         }
4349         gameInfo.site = StrSave(appData.icsHost);
4350         gameInfo.date = PGNDate();
4351         gameInfo.round = StrSave("-");
4352         gameInfo.white = StrSave(white);
4353         gameInfo.black = StrSave(black);
4354         timeControl = basetime * 60 * 1000;
4355         timeControl_2 = 0;
4356         timeIncrement = increment * 1000;
4357         movesPerSession = 0;
4358         gameInfo.timeControl = TimeControlTagValue();
4359         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4360   if (appData.debugMode) {
4361     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4362     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4363     setbuf(debugFP, NULL);
4364   }
4365
4366         gameInfo.outOfBook = NULL;
4367
4368         /* Do we have the ratings? */
4369         if (strcmp(player1Name, white) == 0 &&
4370             strcmp(player2Name, black) == 0) {
4371             if (appData.debugMode)
4372               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4373                       player1Rating, player2Rating);
4374             gameInfo.whiteRating = player1Rating;
4375             gameInfo.blackRating = player2Rating;
4376         } else if (strcmp(player2Name, white) == 0 &&
4377                    strcmp(player1Name, black) == 0) {
4378             if (appData.debugMode)
4379               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4380                       player2Rating, player1Rating);
4381             gameInfo.whiteRating = player2Rating;
4382             gameInfo.blackRating = player1Rating;
4383         }
4384         player1Name[0] = player2Name[0] = NULLCHAR;
4385
4386         /* Silence shouts if requested */
4387         if (appData.quietPlay &&
4388             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4389             SendToICS(ics_prefix);
4390             SendToICS("set shout 0\n");
4391         }
4392     }
4393
4394     /* Deal with midgame name changes */
4395     if (!newGame) {
4396         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4397             if (gameInfo.white) free(gameInfo.white);
4398             gameInfo.white = StrSave(white);
4399         }
4400         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4401             if (gameInfo.black) free(gameInfo.black);
4402             gameInfo.black = StrSave(black);
4403         }
4404     }
4405
4406     /* Throw away game result if anything actually changes in examine mode */
4407     if (gameMode == IcsExamining && !newGame) {
4408         gameInfo.result = GameUnfinished;
4409         if (gameInfo.resultDetails != NULL) {
4410             free(gameInfo.resultDetails);
4411             gameInfo.resultDetails = NULL;
4412         }
4413     }
4414
4415     /* In pausing && IcsExamining mode, we ignore boards coming
4416        in if they are in a different variation than we are. */
4417     if (pauseExamInvalid) return;
4418     if (pausing && gameMode == IcsExamining) {
4419         if (moveNum <= pauseExamForwardMostMove) {
4420             pauseExamInvalid = TRUE;
4421             forwardMostMove = pauseExamForwardMostMove;
4422             return;
4423         }
4424     }
4425
4426   if (appData.debugMode) {
4427     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4428   }
4429     /* Parse the board */
4430     for (k = 0; k < ranks; k++) {
4431       for (j = 0; j < files; j++)
4432         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4433       if(gameInfo.holdingsWidth > 1) {
4434            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4435            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4436       }
4437     }
4438     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4439       board[5][BOARD_RGHT+1] = WhiteAngel;
4440       board[6][BOARD_RGHT+1] = WhiteMarshall;
4441       board[1][0] = BlackMarshall;
4442       board[2][0] = BlackAngel;
4443       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4444     }
4445     CopyBoard(boards[moveNum], board);
4446     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4447     if (moveNum == 0) {
4448         startedFromSetupPosition =
4449           !CompareBoards(board, initialPosition);
4450         if(startedFromSetupPosition)
4451             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4452     }
4453
4454     /* [HGM] Set castling rights. Take the outermost Rooks,
4455        to make it also work for FRC opening positions. Note that board12
4456        is really defective for later FRC positions, as it has no way to
4457        indicate which Rook can castle if they are on the same side of King.
4458        For the initial position we grant rights to the outermost Rooks,
4459        and remember thos rights, and we then copy them on positions
4460        later in an FRC game. This means WB might not recognize castlings with
4461        Rooks that have moved back to their original position as illegal,
4462        but in ICS mode that is not its job anyway.
4463     */
4464     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4465     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4466
4467         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4468             if(board[0][i] == WhiteRook) j = i;
4469         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4470         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4471             if(board[0][i] == WhiteRook) j = i;
4472         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4473         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4474             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4475         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4476         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4477             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4478         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4479
4480         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4481         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4482         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4483             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4484         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4485             if(board[BOARD_HEIGHT-1][k] == bKing)
4486                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4487         if(gameInfo.variant == VariantTwoKings) {
4488             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4489             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4490             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4491         }
4492     } else { int r;
4493         r = boards[moveNum][CASTLING][0] = initialRights[0];
4494         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4495         r = boards[moveNum][CASTLING][1] = initialRights[1];
4496         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4497         r = boards[moveNum][CASTLING][3] = initialRights[3];
4498         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4499         r = boards[moveNum][CASTLING][4] = initialRights[4];
4500         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4501         /* wildcastle kludge: always assume King has rights */
4502         r = boards[moveNum][CASTLING][2] = initialRights[2];
4503         r = boards[moveNum][CASTLING][5] = initialRights[5];
4504     }
4505     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4506     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4507
4508
4509     if (ics_getting_history == H_GOT_REQ_HEADER ||
4510         ics_getting_history == H_GOT_UNREQ_HEADER) {
4511         /* This was an initial position from a move list, not
4512            the current position */
4513         return;
4514     }
4515
4516     /* Update currentMove and known move number limits */
4517     newMove = newGame || moveNum > forwardMostMove;
4518
4519     if (newGame) {
4520         forwardMostMove = backwardMostMove = currentMove = moveNum;
4521         if (gameMode == IcsExamining && moveNum == 0) {
4522           /* Workaround for ICS limitation: we are not told the wild
4523              type when starting to examine a game.  But if we ask for
4524              the move list, the move list header will tell us */
4525             ics_getting_history = H_REQUESTED;
4526             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4527             SendToICS(str);
4528         }
4529     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4530                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4531 #if ZIPPY
4532         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4533         /* [HGM] applied this also to an engine that is silently watching        */
4534         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4535             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4536             gameInfo.variant == currentlyInitializedVariant) {
4537           takeback = forwardMostMove - moveNum;
4538           for (i = 0; i < takeback; i++) {
4539             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4540             SendToProgram("undo\n", &first);
4541           }
4542         }
4543 #endif
4544
4545         forwardMostMove = moveNum;
4546         if (!pausing || currentMove > forwardMostMove)
4547           currentMove = forwardMostMove;
4548     } else {
4549         /* New part of history that is not contiguous with old part */
4550         if (pausing && gameMode == IcsExamining) {
4551             pauseExamInvalid = TRUE;
4552             forwardMostMove = pauseExamForwardMostMove;
4553             return;
4554         }
4555         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4556 #if ZIPPY
4557             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4558                 // [HGM] when we will receive the move list we now request, it will be
4559                 // fed to the engine from the first move on. So if the engine is not
4560                 // in the initial position now, bring it there.
4561                 InitChessProgram(&first, 0);
4562             }
4563 #endif
4564             ics_getting_history = H_REQUESTED;
4565             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4566             SendToICS(str);
4567         }
4568         forwardMostMove = backwardMostMove = currentMove = moveNum;
4569     }
4570
4571     /* Update the clocks */
4572     if (strchr(elapsed_time, '.')) {
4573       /* Time is in ms */
4574       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4575       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4576     } else {
4577       /* Time is in seconds */
4578       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4579       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4580     }
4581
4582
4583 #if ZIPPY
4584     if (appData.zippyPlay && newGame &&
4585         gameMode != IcsObserving && gameMode != IcsIdle &&
4586         gameMode != IcsExamining)
4587       ZippyFirstBoard(moveNum, basetime, increment);
4588 #endif
4589
4590     /* Put the move on the move list, first converting
4591        to canonical algebraic form. */
4592     if (moveNum > 0) {
4593   if (appData.debugMode) {
4594     if (appData.debugMode) { int f = forwardMostMove;
4595         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4596                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4597                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4598     }
4599     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4600     fprintf(debugFP, "moveNum = %d\n", moveNum);
4601     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4602     setbuf(debugFP, NULL);
4603   }
4604         if (moveNum <= backwardMostMove) {
4605             /* We don't know what the board looked like before
4606                this move.  Punt. */
4607           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4608             strcat(parseList[moveNum - 1], " ");
4609             strcat(parseList[moveNum - 1], elapsed_time);
4610             moveList[moveNum - 1][0] = NULLCHAR;
4611         } else if (strcmp(move_str, "none") == 0) {
4612             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4613             /* Again, we don't know what the board looked like;
4614                this is really the start of the game. */
4615             parseList[moveNum - 1][0] = NULLCHAR;
4616             moveList[moveNum - 1][0] = NULLCHAR;
4617             backwardMostMove = moveNum;
4618             startedFromSetupPosition = TRUE;
4619             fromX = fromY = toX = toY = -1;
4620         } else {
4621           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4622           //                 So we parse the long-algebraic move string in stead of the SAN move
4623           int valid; char buf[MSG_SIZ], *prom;
4624
4625           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4626                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4627           // str looks something like "Q/a1-a2"; kill the slash
4628           if(str[1] == '/')
4629             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4630           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4631           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4632                 strcat(buf, prom); // long move lacks promo specification!
4633           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4634                 if(appData.debugMode)
4635                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4636                 safeStrCpy(move_str, buf, MSG_SIZ);
4637           }
4638           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4639                                 &fromX, &fromY, &toX, &toY, &promoChar)
4640                || ParseOneMove(buf, moveNum - 1, &moveType,
4641                                 &fromX, &fromY, &toX, &toY, &promoChar);
4642           // end of long SAN patch
4643           if (valid) {
4644             (void) CoordsToAlgebraic(boards[moveNum - 1],
4645                                      PosFlags(moveNum - 1),
4646                                      fromY, fromX, toY, toX, promoChar,
4647                                      parseList[moveNum-1]);
4648             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4649               case MT_NONE:
4650               case MT_STALEMATE:
4651               default:
4652                 break;
4653               case MT_CHECK:
4654                 if(gameInfo.variant != VariantShogi)
4655                     strcat(parseList[moveNum - 1], "+");
4656                 break;
4657               case MT_CHECKMATE:
4658               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4659                 strcat(parseList[moveNum - 1], "#");
4660                 break;
4661             }
4662             strcat(parseList[moveNum - 1], " ");
4663             strcat(parseList[moveNum - 1], elapsed_time);
4664             /* currentMoveString is set as a side-effect of ParseOneMove */
4665             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4666             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4667             strcat(moveList[moveNum - 1], "\n");
4668
4669             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4670                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4671               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4672                 ChessSquare old, new = boards[moveNum][k][j];
4673                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4674                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4675                   if(old == new) continue;
4676                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4677                   else if(new == WhiteWazir || new == BlackWazir) {
4678                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4679                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4680                       else boards[moveNum][k][j] = old; // preserve type of Gold
4681                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4682                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4683               }
4684           } else {
4685             /* Move from ICS was illegal!?  Punt. */
4686             if (appData.debugMode) {
4687               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4688               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4689             }
4690             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4691             strcat(parseList[moveNum - 1], " ");
4692             strcat(parseList[moveNum - 1], elapsed_time);
4693             moveList[moveNum - 1][0] = NULLCHAR;
4694             fromX = fromY = toX = toY = -1;
4695           }
4696         }
4697   if (appData.debugMode) {
4698     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4699     setbuf(debugFP, NULL);
4700   }
4701
4702 #if ZIPPY
4703         /* Send move to chess program (BEFORE animating it). */
4704         if (appData.zippyPlay && !newGame && newMove &&
4705            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4706
4707             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4708                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4709                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4710                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4711                             move_str);
4712                     DisplayError(str, 0);
4713                 } else {
4714                     if (first.sendTime) {
4715                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4716                     }
4717                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4718                     if (firstMove && !bookHit) {
4719                         firstMove = FALSE;
4720                         if (first.useColors) {
4721                           SendToProgram(gameMode == IcsPlayingWhite ?
4722                                         "white\ngo\n" :
4723                                         "black\ngo\n", &first);
4724                         } else {
4725                           SendToProgram("go\n", &first);
4726                         }
4727                         first.maybeThinking = TRUE;
4728                     }
4729                 }
4730             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4731               if (moveList[moveNum - 1][0] == NULLCHAR) {
4732                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4733                 DisplayError(str, 0);
4734               } else {
4735                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4736                 SendMoveToProgram(moveNum - 1, &first);
4737               }
4738             }
4739         }
4740 #endif
4741     }
4742
4743     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4744         /* If move comes from a remote source, animate it.  If it
4745            isn't remote, it will have already been animated. */
4746         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4747             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4748         }
4749         if (!pausing && appData.highlightLastMove) {
4750             SetHighlights(fromX, fromY, toX, toY);
4751         }
4752     }
4753
4754     /* Start the clocks */
4755     whiteFlag = blackFlag = FALSE;
4756     appData.clockMode = !(basetime == 0 && increment == 0);
4757     if (ticking == 0) {
4758       ics_clock_paused = TRUE;
4759       StopClocks();
4760     } else if (ticking == 1) {
4761       ics_clock_paused = FALSE;
4762     }
4763     if (gameMode == IcsIdle ||
4764         relation == RELATION_OBSERVING_STATIC ||
4765         relation == RELATION_EXAMINING ||
4766         ics_clock_paused)
4767       DisplayBothClocks();
4768     else
4769       StartClocks();
4770
4771     /* Display opponents and material strengths */
4772     if (gameInfo.variant != VariantBughouse &&
4773         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4774         if (tinyLayout || smallLayout) {
4775             if(gameInfo.variant == VariantNormal)
4776               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4777                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4778                     basetime, increment);
4779             else
4780               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4781                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4782                     basetime, increment, (int) gameInfo.variant);
4783         } else {
4784             if(gameInfo.variant == VariantNormal)
4785               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4786                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4787                     basetime, increment);
4788             else
4789               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4790                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4791                     basetime, increment, VariantName(gameInfo.variant));
4792         }
4793         DisplayTitle(str);
4794   if (appData.debugMode) {
4795     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4796   }
4797     }
4798
4799
4800     /* Display the board */
4801     if (!pausing && !appData.noGUI) {
4802
4803       if (appData.premove)
4804           if (!gotPremove ||
4805              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4806              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4807               ClearPremoveHighlights();
4808
4809       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4810         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4811       DrawPosition(j, boards[currentMove]);
4812
4813       DisplayMove(moveNum - 1);
4814       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4815             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4816               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4817         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4818       }
4819     }
4820
4821     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4822 #if ZIPPY
4823     if(bookHit) { // [HGM] book: simulate book reply
4824         static char bookMove[MSG_SIZ]; // a bit generous?
4825
4826         programStats.nodes = programStats.depth = programStats.time =
4827         programStats.score = programStats.got_only_move = 0;
4828         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4829
4830         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4831         strcat(bookMove, bookHit);
4832         HandleMachineMove(bookMove, &first);
4833     }
4834 #endif
4835 }
4836
4837 void
4838 GetMoveListEvent ()
4839 {
4840     char buf[MSG_SIZ];
4841     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4842         ics_getting_history = H_REQUESTED;
4843         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4844         SendToICS(buf);
4845     }
4846 }
4847
4848 void
4849 AnalysisPeriodicEvent (int force)
4850 {
4851     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4852          && !force) || !appData.periodicUpdates)
4853       return;
4854
4855     /* Send . command to Crafty to collect stats */
4856     SendToProgram(".\n", &first);
4857
4858     /* Don't send another until we get a response (this makes
4859        us stop sending to old Crafty's which don't understand
4860        the "." command (sending illegal cmds resets node count & time,
4861        which looks bad)) */
4862     programStats.ok_to_send = 0;
4863 }
4864
4865 void
4866 ics_update_width (int new_width)
4867 {
4868         ics_printf("set width %d\n", new_width);
4869 }
4870
4871 void
4872 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4873 {
4874     char buf[MSG_SIZ];
4875
4876     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4877         // null move in variant where engine does not understand it (for analysis purposes)
4878         SendBoard(cps, moveNum + 1); // send position after move in stead.
4879         return;
4880     }
4881     if (cps->useUsermove) {
4882       SendToProgram("usermove ", cps);
4883     }
4884     if (cps->useSAN) {
4885       char *space;
4886       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4887         int len = space - parseList[moveNum];
4888         memcpy(buf, parseList[moveNum], len);
4889         buf[len++] = '\n';
4890         buf[len] = NULLCHAR;
4891       } else {
4892         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4893       }
4894       SendToProgram(buf, cps);
4895     } else {
4896       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4897         AlphaRank(moveList[moveNum], 4);
4898         SendToProgram(moveList[moveNum], cps);
4899         AlphaRank(moveList[moveNum], 4); // and back
4900       } else
4901       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4902        * the engine. It would be nice to have a better way to identify castle
4903        * moves here. */
4904       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4905                                                                          && cps->useOOCastle) {
4906         int fromX = moveList[moveNum][0] - AAA;
4907         int fromY = moveList[moveNum][1] - ONE;
4908         int toX = moveList[moveNum][2] - AAA;
4909         int toY = moveList[moveNum][3] - ONE;
4910         if((boards[moveNum][fromY][fromX] == WhiteKing
4911             && boards[moveNum][toY][toX] == WhiteRook)
4912            || (boards[moveNum][fromY][fromX] == BlackKing
4913                && boards[moveNum][toY][toX] == BlackRook)) {
4914           if(toX > fromX) SendToProgram("O-O\n", cps);
4915           else SendToProgram("O-O-O\n", cps);
4916         }
4917         else SendToProgram(moveList[moveNum], cps);
4918       } else
4919       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4920         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4921           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4922           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4923                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4924         } else
4925           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4926                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4927         SendToProgram(buf, cps);
4928       }
4929       else SendToProgram(moveList[moveNum], cps);
4930       /* End of additions by Tord */
4931     }
4932
4933     /* [HGM] setting up the opening has brought engine in force mode! */
4934     /*       Send 'go' if we are in a mode where machine should play. */
4935     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4936         (gameMode == TwoMachinesPlay   ||
4937 #if ZIPPY
4938          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4939 #endif
4940          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4941         SendToProgram("go\n", cps);
4942   if (appData.debugMode) {
4943     fprintf(debugFP, "(extra)\n");
4944   }
4945     }
4946     setboardSpoiledMachineBlack = 0;
4947 }
4948
4949 void
4950 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4951 {
4952     char user_move[MSG_SIZ];
4953     char suffix[4];
4954
4955     if(gameInfo.variant == VariantSChess && promoChar) {
4956         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4957         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4958     } else suffix[0] = NULLCHAR;
4959
4960     switch (moveType) {
4961       default:
4962         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4963                 (int)moveType, fromX, fromY, toX, toY);
4964         DisplayError(user_move + strlen("say "), 0);
4965         break;
4966       case WhiteKingSideCastle:
4967       case BlackKingSideCastle:
4968       case WhiteQueenSideCastleWild:
4969       case BlackQueenSideCastleWild:
4970       /* PUSH Fabien */
4971       case WhiteHSideCastleFR:
4972       case BlackHSideCastleFR:
4973       /* POP Fabien */
4974         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4975         break;
4976       case WhiteQueenSideCastle:
4977       case BlackQueenSideCastle:
4978       case WhiteKingSideCastleWild:
4979       case BlackKingSideCastleWild:
4980       /* PUSH Fabien */
4981       case WhiteASideCastleFR:
4982       case BlackASideCastleFR:
4983       /* POP Fabien */
4984         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
4985         break;
4986       case WhiteNonPromotion:
4987       case BlackNonPromotion:
4988         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4989         break;
4990       case WhitePromotion:
4991       case BlackPromotion:
4992         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4993           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4994                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4995                 PieceToChar(WhiteFerz));
4996         else if(gameInfo.variant == VariantGreat)
4997           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4998                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4999                 PieceToChar(WhiteMan));
5000         else
5001           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5002                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5003                 promoChar);
5004         break;
5005       case WhiteDrop:
5006       case BlackDrop:
5007       drop:
5008         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5009                  ToUpper(PieceToChar((ChessSquare) fromX)),
5010                  AAA + toX, ONE + toY);
5011         break;
5012       case IllegalMove:  /* could be a variant we don't quite understand */
5013         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5014       case NormalMove:
5015       case WhiteCapturesEnPassant:
5016       case BlackCapturesEnPassant:
5017         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5018                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5019         break;
5020     }
5021     SendToICS(user_move);
5022     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5023         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5024 }
5025
5026 void
5027 UploadGameEvent ()
5028 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5029     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5030     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5031     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5032       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5033       return;
5034     }
5035     if(gameMode != IcsExamining) { // is this ever not the case?
5036         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5037
5038         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5039           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5040         } else { // on FICS we must first go to general examine mode
5041           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5042         }
5043         if(gameInfo.variant != VariantNormal) {
5044             // try figure out wild number, as xboard names are not always valid on ICS
5045             for(i=1; i<=36; i++) {
5046               snprintf(buf, MSG_SIZ, "wild/%d", i);
5047                 if(StringToVariant(buf) == gameInfo.variant) break;
5048             }
5049             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5050             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5051             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5052         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5053         SendToICS(ics_prefix);
5054         SendToICS(buf);
5055         if(startedFromSetupPosition || backwardMostMove != 0) {
5056           fen = PositionToFEN(backwardMostMove, NULL);
5057           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5058             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5059             SendToICS(buf);
5060           } else { // FICS: everything has to set by separate bsetup commands
5061             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5062             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5063             SendToICS(buf);
5064             if(!WhiteOnMove(backwardMostMove)) {
5065                 SendToICS("bsetup tomove black\n");
5066             }
5067             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5068             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5069             SendToICS(buf);
5070             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5071             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5072             SendToICS(buf);
5073             i = boards[backwardMostMove][EP_STATUS];
5074             if(i >= 0) { // set e.p.
5075               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5076                 SendToICS(buf);
5077             }
5078             bsetup++;
5079           }
5080         }
5081       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5082             SendToICS("bsetup done\n"); // switch to normal examining.
5083     }
5084     for(i = backwardMostMove; i<last; i++) {
5085         char buf[20];
5086         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5087         SendToICS(buf);
5088     }
5089     SendToICS(ics_prefix);
5090     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5091 }
5092
5093 void
5094 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5095 {
5096     if (rf == DROP_RANK) {
5097       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5098       sprintf(move, "%c@%c%c\n",
5099                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5100     } else {
5101         if (promoChar == 'x' || promoChar == NULLCHAR) {
5102           sprintf(move, "%c%c%c%c\n",
5103                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5104         } else {
5105             sprintf(move, "%c%c%c%c%c\n",
5106                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5107         }
5108     }
5109 }
5110
5111 void
5112 ProcessICSInitScript (FILE *f)
5113 {
5114     char buf[MSG_SIZ];
5115
5116     while (fgets(buf, MSG_SIZ, f)) {
5117         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5118     }
5119
5120     fclose(f);
5121 }
5122
5123
5124 static int lastX, lastY, selectFlag, dragging;
5125
5126 void
5127 Sweep (int step)
5128 {
5129     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5130     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5131     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5132     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5133     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5134     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5135     do {
5136         promoSweep -= step;
5137         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5138         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5139         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5140         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5141         if(!step) step = -1;
5142     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5143             appData.testLegality && (promoSweep == king ||
5144             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5145     ChangeDragPiece(promoSweep);
5146 }
5147
5148 int
5149 PromoScroll (int x, int y)
5150 {
5151   int step = 0;
5152
5153   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5154   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5155   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5156   if(!step) return FALSE;
5157   lastX = x; lastY = y;
5158   if((promoSweep < BlackPawn) == flipView) step = -step;
5159   if(step > 0) selectFlag = 1;
5160   if(!selectFlag) Sweep(step);
5161   return FALSE;
5162 }
5163
5164 void
5165 NextPiece (int step)
5166 {
5167     ChessSquare piece = boards[currentMove][toY][toX];
5168     do {
5169         pieceSweep -= step;
5170         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5171         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5172         if(!step) step = -1;
5173     } while(PieceToChar(pieceSweep) == '.');
5174     boards[currentMove][toY][toX] = pieceSweep;
5175     DrawPosition(FALSE, boards[currentMove]);
5176     boards[currentMove][toY][toX] = piece;
5177 }
5178 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5179 void
5180 AlphaRank (char *move, int n)
5181 {
5182 //    char *p = move, c; int x, y;
5183
5184     if (appData.debugMode) {
5185         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5186     }
5187
5188     if(move[1]=='*' &&
5189        move[2]>='0' && move[2]<='9' &&
5190        move[3]>='a' && move[3]<='x'    ) {
5191         move[1] = '@';
5192         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5193         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5194     } else
5195     if(move[0]>='0' && move[0]<='9' &&
5196        move[1]>='a' && move[1]<='x' &&
5197        move[2]>='0' && move[2]<='9' &&
5198        move[3]>='a' && move[3]<='x'    ) {
5199         /* input move, Shogi -> normal */
5200         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5201         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5202         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5203         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5204     } else
5205     if(move[1]=='@' &&
5206        move[3]>='0' && move[3]<='9' &&
5207        move[2]>='a' && move[2]<='x'    ) {
5208         move[1] = '*';
5209         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5210         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5211     } else
5212     if(
5213        move[0]>='a' && move[0]<='x' &&
5214        move[3]>='0' && move[3]<='9' &&
5215        move[2]>='a' && move[2]<='x'    ) {
5216          /* output move, normal -> Shogi */
5217         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5218         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5219         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5220         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5221         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5222     }
5223     if (appData.debugMode) {
5224         fprintf(debugFP, "   out = '%s'\n", move);
5225     }
5226 }
5227
5228 char yy_textstr[8000];
5229
5230 /* Parser for moves from gnuchess, ICS, or user typein box */
5231 Boolean
5232 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5233 {
5234     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5235
5236     switch (*moveType) {
5237       case WhitePromotion:
5238       case BlackPromotion:
5239       case WhiteNonPromotion:
5240       case BlackNonPromotion:
5241       case NormalMove:
5242       case WhiteCapturesEnPassant:
5243       case BlackCapturesEnPassant:
5244       case WhiteKingSideCastle:
5245       case WhiteQueenSideCastle:
5246       case BlackKingSideCastle:
5247       case BlackQueenSideCastle:
5248       case WhiteKingSideCastleWild:
5249       case WhiteQueenSideCastleWild:
5250       case BlackKingSideCastleWild:
5251       case BlackQueenSideCastleWild:
5252       /* Code added by Tord: */
5253       case WhiteHSideCastleFR:
5254       case WhiteASideCastleFR:
5255       case BlackHSideCastleFR:
5256       case BlackASideCastleFR:
5257       /* End of code added by Tord */
5258       case IllegalMove:         /* bug or odd chess variant */
5259         *fromX = currentMoveString[0] - AAA;
5260         *fromY = currentMoveString[1] - ONE;
5261         *toX = currentMoveString[2] - AAA;
5262         *toY = currentMoveString[3] - ONE;
5263         *promoChar = currentMoveString[4];
5264         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5265             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5266     if (appData.debugMode) {
5267         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5268     }
5269             *fromX = *fromY = *toX = *toY = 0;
5270             return FALSE;
5271         }
5272         if (appData.testLegality) {
5273           return (*moveType != IllegalMove);
5274         } else {
5275           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5276                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5277         }
5278
5279       case WhiteDrop:
5280       case BlackDrop:
5281         *fromX = *moveType == WhiteDrop ?
5282           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5283           (int) CharToPiece(ToLower(currentMoveString[0]));
5284         *fromY = DROP_RANK;
5285         *toX = currentMoveString[2] - AAA;
5286         *toY = currentMoveString[3] - ONE;
5287         *promoChar = NULLCHAR;
5288         return TRUE;
5289
5290       case AmbiguousMove:
5291       case ImpossibleMove:
5292       case EndOfFile:
5293       case ElapsedTime:
5294       case Comment:
5295       case PGNTag:
5296       case NAG:
5297       case WhiteWins:
5298       case BlackWins:
5299       case GameIsDrawn:
5300       default:
5301     if (appData.debugMode) {
5302         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5303     }
5304         /* bug? */
5305         *fromX = *fromY = *toX = *toY = 0;
5306         *promoChar = NULLCHAR;
5307         return FALSE;
5308     }
5309 }
5310
5311 Boolean pushed = FALSE;
5312 char *lastParseAttempt;
5313
5314 void
5315 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5316 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5317   int fromX, fromY, toX, toY; char promoChar;
5318   ChessMove moveType;
5319   Boolean valid;
5320   int nr = 0;
5321
5322   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5323     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5324     pushed = TRUE;
5325   }
5326   endPV = forwardMostMove;
5327   do {
5328     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5329     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5330     lastParseAttempt = pv;
5331     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5332 if(appData.debugMode){
5333 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
5334 }
5335     if(!valid && nr == 0 &&
5336        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5337         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5338         // Hande case where played move is different from leading PV move
5339         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5340         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5341         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5342         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5343           endPV += 2; // if position different, keep this
5344           moveList[endPV-1][0] = fromX + AAA;
5345           moveList[endPV-1][1] = fromY + ONE;
5346           moveList[endPV-1][2] = toX + AAA;
5347           moveList[endPV-1][3] = toY + ONE;
5348           parseList[endPV-1][0] = NULLCHAR;
5349           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5350         }
5351       }
5352     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5353     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5354     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5355     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5356         valid++; // allow comments in PV
5357         continue;
5358     }
5359     nr++;
5360     if(endPV+1 > framePtr) break; // no space, truncate
5361     if(!valid) break;
5362     endPV++;
5363     CopyBoard(boards[endPV], boards[endPV-1]);
5364     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5365     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5366     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5367     CoordsToAlgebraic(boards[endPV - 1],
5368                              PosFlags(endPV - 1),
5369                              fromY, fromX, toY, toX, promoChar,
5370                              parseList[endPV - 1]);
5371   } while(valid);
5372   if(atEnd == 2) return; // used hidden, for PV conversion
5373   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5374   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5375   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5376                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5377   DrawPosition(TRUE, boards[currentMove]);
5378 }
5379
5380 int
5381 MultiPV (ChessProgramState *cps)
5382 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5383         int i;
5384         for(i=0; i<cps->nrOptions; i++)
5385             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5386                 return i;
5387         return -1;
5388 }
5389
5390 Boolean
5391 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5392 {
5393         int startPV, multi, lineStart, origIndex = index;
5394         char *p, buf2[MSG_SIZ];
5395
5396         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5397         lastX = x; lastY = y;
5398         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5399         lineStart = startPV = index;
5400         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5401         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5402         index = startPV;
5403         do{ while(buf[index] && buf[index] != '\n') index++;
5404         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5405         buf[index] = 0;
5406         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5407                 int n = first.option[multi].value;
5408                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5409                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5410                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5411                 first.option[multi].value = n;
5412                 *start = *end = 0;
5413                 return FALSE;
5414         }
5415         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5416         *start = startPV; *end = index-1;
5417         return TRUE;
5418 }
5419
5420 char *
5421 PvToSAN (char *pv)
5422 {
5423         static char buf[10*MSG_SIZ];
5424         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5425         *buf = NULLCHAR;
5426         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5427         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5428         for(i = forwardMostMove; i<endPV; i++){
5429             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5430             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5431             k += strlen(buf+k);
5432         }
5433         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5434         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5435         endPV = savedEnd;
5436         return buf;
5437 }
5438
5439 Boolean
5440 LoadPV (int x, int y)
5441 { // called on right mouse click to load PV
5442   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5443   lastX = x; lastY = y;
5444   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5445   return TRUE;
5446 }
5447
5448 void
5449 UnLoadPV ()
5450 {
5451   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5452   if(endPV < 0) return;
5453   endPV = -1;
5454   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5455         Boolean saveAnimate = appData.animate;
5456         if(pushed) {
5457             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5458                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5459             } else storedGames--; // abandon shelved tail of original game
5460         }
5461         pushed = FALSE;
5462         forwardMostMove = currentMove;
5463         currentMove = oldFMM;
5464         appData.animate = FALSE;
5465         ToNrEvent(forwardMostMove);
5466         appData.animate = saveAnimate;
5467   }
5468   currentMove = forwardMostMove;
5469   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5470   ClearPremoveHighlights();
5471   DrawPosition(TRUE, boards[currentMove]);
5472 }
5473
5474 void
5475 MovePV (int x, int y, int h)
5476 { // step through PV based on mouse coordinates (called on mouse move)
5477   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5478
5479   // we must somehow check if right button is still down (might be released off board!)
5480   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5481   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5482   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5483   if(!step) return;
5484   lastX = x; lastY = y;
5485
5486   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5487   if(endPV < 0) return;
5488   if(y < margin) step = 1; else
5489   if(y > h - margin) step = -1;
5490   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5491   currentMove += step;
5492   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5493   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5494                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5495   DrawPosition(FALSE, boards[currentMove]);
5496 }
5497
5498
5499 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5500 // All positions will have equal probability, but the current method will not provide a unique
5501 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5502 #define DARK 1
5503 #define LITE 2
5504 #define ANY 3
5505
5506 int squaresLeft[4];
5507 int piecesLeft[(int)BlackPawn];
5508 int seed, nrOfShuffles;
5509
5510 void
5511 GetPositionNumber ()
5512 {       // sets global variable seed
5513         int i;
5514
5515         seed = appData.defaultFrcPosition;
5516         if(seed < 0) { // randomize based on time for negative FRC position numbers
5517                 for(i=0; i<50; i++) seed += random();
5518                 seed = random() ^ random() >> 8 ^ random() << 8;
5519                 if(seed<0) seed = -seed;
5520         }
5521 }
5522
5523 int
5524 put (Board board, int pieceType, int rank, int n, int shade)
5525 // put the piece on the (n-1)-th empty squares of the given shade
5526 {
5527         int i;
5528
5529         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5530                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5531                         board[rank][i] = (ChessSquare) pieceType;
5532                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5533                         squaresLeft[ANY]--;
5534                         piecesLeft[pieceType]--;
5535                         return i;
5536                 }
5537         }
5538         return -1;
5539 }
5540
5541
5542 void
5543 AddOnePiece (Board board, int pieceType, int rank, int shade)
5544 // calculate where the next piece goes, (any empty square), and put it there
5545 {
5546         int i;
5547
5548         i = seed % squaresLeft[shade];
5549         nrOfShuffles *= squaresLeft[shade];
5550         seed /= squaresLeft[shade];
5551         put(board, pieceType, rank, i, shade);
5552 }
5553
5554 void
5555 AddTwoPieces (Board board, int pieceType, int rank)
5556 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5557 {
5558         int i, n=squaresLeft[ANY], j=n-1, k;
5559
5560         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5561         i = seed % k;  // pick one
5562         nrOfShuffles *= k;
5563         seed /= k;
5564         while(i >= j) i -= j--;
5565         j = n - 1 - j; i += j;
5566         put(board, pieceType, rank, j, ANY);
5567         put(board, pieceType, rank, i, ANY);
5568 }
5569
5570 void
5571 SetUpShuffle (Board board, int number)
5572 {
5573         int i, p, first=1;
5574
5575         GetPositionNumber(); nrOfShuffles = 1;
5576
5577         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5578         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5579         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5580
5581         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5582
5583         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5584             p = (int) board[0][i];
5585             if(p < (int) BlackPawn) piecesLeft[p] ++;
5586             board[0][i] = EmptySquare;
5587         }
5588
5589         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5590             // shuffles restricted to allow normal castling put KRR first
5591             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5592                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5593             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5594                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5595             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5596                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5597             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5598                 put(board, WhiteRook, 0, 0, ANY);
5599             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5600         }
5601
5602         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5603             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5604             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5605                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5606                 while(piecesLeft[p] >= 2) {
5607                     AddOnePiece(board, p, 0, LITE);
5608                     AddOnePiece(board, p, 0, DARK);
5609                 }
5610                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5611             }
5612
5613         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5614             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5615             // but we leave King and Rooks for last, to possibly obey FRC restriction
5616             if(p == (int)WhiteRook) continue;
5617             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5618             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5619         }
5620
5621         // now everything is placed, except perhaps King (Unicorn) and Rooks
5622
5623         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5624             // Last King gets castling rights
5625             while(piecesLeft[(int)WhiteUnicorn]) {
5626                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5627                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5628             }
5629
5630             while(piecesLeft[(int)WhiteKing]) {
5631                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5632                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5633             }
5634
5635
5636         } else {
5637             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5638             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5639         }
5640
5641         // Only Rooks can be left; simply place them all
5642         while(piecesLeft[(int)WhiteRook]) {
5643                 i = put(board, WhiteRook, 0, 0, ANY);
5644                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5645                         if(first) {
5646                                 first=0;
5647                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5648                         }
5649                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5650                 }
5651         }
5652         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5653             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5654         }
5655
5656         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5657 }
5658
5659 int
5660 SetCharTable (char *table, const char * map)
5661 /* [HGM] moved here from winboard.c because of its general usefulness */
5662 /*       Basically a safe strcpy that uses the last character as King */
5663 {
5664     int result = FALSE; int NrPieces;
5665
5666     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5667                     && NrPieces >= 12 && !(NrPieces&1)) {
5668         int i; /* [HGM] Accept even length from 12 to 34 */
5669
5670         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5671         for( i=0; i<NrPieces/2-1; i++ ) {
5672             table[i] = map[i];
5673             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5674         }
5675         table[(int) WhiteKing]  = map[NrPieces/2-1];
5676         table[(int) BlackKing]  = map[NrPieces-1];
5677
5678         result = TRUE;
5679     }
5680
5681     return result;
5682 }
5683
5684 void
5685 Prelude (Board board)
5686 {       // [HGM] superchess: random selection of exo-pieces
5687         int i, j, k; ChessSquare p;
5688         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5689
5690         GetPositionNumber(); // use FRC position number
5691
5692         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5693             SetCharTable(pieceToChar, appData.pieceToCharTable);
5694             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5695                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5696         }
5697
5698         j = seed%4;                 seed /= 4;
5699         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5700         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5701         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5702         j = seed%3 + (seed%3 >= j); seed /= 3;
5703         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5704         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5705         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5706         j = seed%3;                 seed /= 3;
5707         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5708         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5709         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5710         j = seed%2 + (seed%2 >= j); seed /= 2;
5711         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5712         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5713         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5714         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5715         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5716         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5717         put(board, exoPieces[0],    0, 0, ANY);
5718         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5719 }
5720
5721 void
5722 InitPosition (int redraw)
5723 {
5724     ChessSquare (* pieces)[BOARD_FILES];
5725     int i, j, pawnRow, overrule,
5726     oldx = gameInfo.boardWidth,
5727     oldy = gameInfo.boardHeight,
5728     oldh = gameInfo.holdingsWidth;
5729     static int oldv;
5730
5731     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5732
5733     /* [AS] Initialize pv info list [HGM] and game status */
5734     {
5735         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5736             pvInfoList[i].depth = 0;
5737             boards[i][EP_STATUS] = EP_NONE;
5738             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5739         }
5740
5741         initialRulePlies = 0; /* 50-move counter start */
5742
5743         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5744         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5745     }
5746
5747
5748     /* [HGM] logic here is completely changed. In stead of full positions */
5749     /* the initialized data only consist of the two backranks. The switch */
5750     /* selects which one we will use, which is than copied to the Board   */
5751     /* initialPosition, which for the rest is initialized by Pawns and    */
5752     /* empty squares. This initial position is then copied to boards[0],  */
5753     /* possibly after shuffling, so that it remains available.            */
5754
5755     gameInfo.holdingsWidth = 0; /* default board sizes */
5756     gameInfo.boardWidth    = 8;
5757     gameInfo.boardHeight   = 8;
5758     gameInfo.holdingsSize  = 0;
5759     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5760     for(i=0; i<BOARD_FILES-2; i++)
5761       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5762     initialPosition[EP_STATUS] = EP_NONE;
5763     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5764     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5765          SetCharTable(pieceNickName, appData.pieceNickNames);
5766     else SetCharTable(pieceNickName, "............");
5767     pieces = FIDEArray;
5768
5769     switch (gameInfo.variant) {
5770     case VariantFischeRandom:
5771       shuffleOpenings = TRUE;
5772     default:
5773       break;
5774     case VariantShatranj:
5775       pieces = ShatranjArray;
5776       nrCastlingRights = 0;
5777       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5778       break;
5779     case VariantMakruk:
5780       pieces = makrukArray;
5781       nrCastlingRights = 0;
5782       startedFromSetupPosition = TRUE;
5783       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5784       break;
5785     case VariantTwoKings:
5786       pieces = twoKingsArray;
5787       break;
5788     case VariantGrand:
5789       pieces = GrandArray;
5790       nrCastlingRights = 0;
5791       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5792       gameInfo.boardWidth = 10;
5793       gameInfo.boardHeight = 10;
5794       gameInfo.holdingsSize = 7;
5795       break;
5796     case VariantCapaRandom:
5797       shuffleOpenings = TRUE;
5798     case VariantCapablanca:
5799       pieces = CapablancaArray;
5800       gameInfo.boardWidth = 10;
5801       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5802       break;
5803     case VariantGothic:
5804       pieces = GothicArray;
5805       gameInfo.boardWidth = 10;
5806       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5807       break;
5808     case VariantSChess:
5809       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5810       gameInfo.holdingsSize = 7;
5811       break;
5812     case VariantJanus:
5813       pieces = JanusArray;
5814       gameInfo.boardWidth = 10;
5815       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5816       nrCastlingRights = 6;
5817         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5818         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5819         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5820         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5821         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5822         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5823       break;
5824     case VariantFalcon:
5825       pieces = FalconArray;
5826       gameInfo.boardWidth = 10;
5827       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5828       break;
5829     case VariantXiangqi:
5830       pieces = XiangqiArray;
5831       gameInfo.boardWidth  = 9;
5832       gameInfo.boardHeight = 10;
5833       nrCastlingRights = 0;
5834       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5835       break;
5836     case VariantShogi:
5837       pieces = ShogiArray;
5838       gameInfo.boardWidth  = 9;
5839       gameInfo.boardHeight = 9;
5840       gameInfo.holdingsSize = 7;
5841       nrCastlingRights = 0;
5842       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5843       break;
5844     case VariantCourier:
5845       pieces = CourierArray;
5846       gameInfo.boardWidth  = 12;
5847       nrCastlingRights = 0;
5848       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5849       break;
5850     case VariantKnightmate:
5851       pieces = KnightmateArray;
5852       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5853       break;
5854     case VariantSpartan:
5855       pieces = SpartanArray;
5856       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5857       break;
5858     case VariantFairy:
5859       pieces = fairyArray;
5860       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5861       break;
5862     case VariantGreat:
5863       pieces = GreatArray;
5864       gameInfo.boardWidth = 10;
5865       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5866       gameInfo.holdingsSize = 8;
5867       break;
5868     case VariantSuper:
5869       pieces = FIDEArray;
5870       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5871       gameInfo.holdingsSize = 8;
5872       startedFromSetupPosition = TRUE;
5873       break;
5874     case VariantCrazyhouse:
5875     case VariantBughouse:
5876       pieces = FIDEArray;
5877       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5878       gameInfo.holdingsSize = 5;
5879       break;
5880     case VariantWildCastle:
5881       pieces = FIDEArray;
5882       /* !!?shuffle with kings guaranteed to be on d or e file */
5883       shuffleOpenings = 1;
5884       break;
5885     case VariantNoCastle:
5886       pieces = FIDEArray;
5887       nrCastlingRights = 0;
5888       /* !!?unconstrained back-rank shuffle */
5889       shuffleOpenings = 1;
5890       break;
5891     }
5892
5893     overrule = 0;
5894     if(appData.NrFiles >= 0) {
5895         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5896         gameInfo.boardWidth = appData.NrFiles;
5897     }
5898     if(appData.NrRanks >= 0) {
5899         gameInfo.boardHeight = appData.NrRanks;
5900     }
5901     if(appData.holdingsSize >= 0) {
5902         i = appData.holdingsSize;
5903         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5904         gameInfo.holdingsSize = i;
5905     }
5906     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5907     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5908         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5909
5910     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5911     if(pawnRow < 1) pawnRow = 1;
5912     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5913
5914     /* User pieceToChar list overrules defaults */
5915     if(appData.pieceToCharTable != NULL)
5916         SetCharTable(pieceToChar, appData.pieceToCharTable);
5917
5918     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5919
5920         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5921             s = (ChessSquare) 0; /* account holding counts in guard band */
5922         for( i=0; i<BOARD_HEIGHT; i++ )
5923             initialPosition[i][j] = s;
5924
5925         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5926         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5927         initialPosition[pawnRow][j] = WhitePawn;
5928         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5929         if(gameInfo.variant == VariantXiangqi) {
5930             if(j&1) {
5931                 initialPosition[pawnRow][j] =
5932                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5933                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5934                    initialPosition[2][j] = WhiteCannon;
5935                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5936                 }
5937             }
5938         }
5939         if(gameInfo.variant == VariantGrand) {
5940             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5941                initialPosition[0][j] = WhiteRook;
5942                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5943             }
5944         }
5945         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5946     }
5947     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5948
5949             j=BOARD_LEFT+1;
5950             initialPosition[1][j] = WhiteBishop;
5951             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5952             j=BOARD_RGHT-2;
5953             initialPosition[1][j] = WhiteRook;
5954             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5955     }
5956
5957     if( nrCastlingRights == -1) {
5958         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5959         /*       This sets default castling rights from none to normal corners   */
5960         /* Variants with other castling rights must set them themselves above    */
5961         nrCastlingRights = 6;
5962
5963         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5964         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5965         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5966         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5967         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5968         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5969      }
5970
5971      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5972      if(gameInfo.variant == VariantGreat) { // promotion commoners
5973         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5974         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5975         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5976         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5977      }
5978      if( gameInfo.variant == VariantSChess ) {
5979       initialPosition[1][0] = BlackMarshall;
5980       initialPosition[2][0] = BlackAngel;
5981       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5982       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5983       initialPosition[1][1] = initialPosition[2][1] = 
5984       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5985      }
5986   if (appData.debugMode) {
5987     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5988   }
5989     if(shuffleOpenings) {
5990         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5991         startedFromSetupPosition = TRUE;
5992     }
5993     if(startedFromPositionFile) {
5994       /* [HGM] loadPos: use PositionFile for every new game */
5995       CopyBoard(initialPosition, filePosition);
5996       for(i=0; i<nrCastlingRights; i++)
5997           initialRights[i] = filePosition[CASTLING][i];
5998       startedFromSetupPosition = TRUE;
5999     }
6000
6001     CopyBoard(boards[0], initialPosition);
6002
6003     if(oldx != gameInfo.boardWidth ||
6004        oldy != gameInfo.boardHeight ||
6005        oldv != gameInfo.variant ||
6006        oldh != gameInfo.holdingsWidth
6007                                          )
6008             InitDrawingSizes(-2 ,0);
6009
6010     oldv = gameInfo.variant;
6011     if (redraw)
6012       DrawPosition(TRUE, boards[currentMove]);
6013 }
6014
6015 void
6016 SendBoard (ChessProgramState *cps, int moveNum)
6017 {
6018     char message[MSG_SIZ];
6019
6020     if (cps->useSetboard) {
6021       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6022       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6023       SendToProgram(message, cps);
6024       free(fen);
6025
6026     } else {
6027       ChessSquare *bp;
6028       int i, j, left=0, right=BOARD_WIDTH;
6029       /* Kludge to set black to move, avoiding the troublesome and now
6030        * deprecated "black" command.
6031        */
6032       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6033         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6034
6035       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6036
6037       SendToProgram("edit\n", cps);
6038       SendToProgram("#\n", cps);
6039       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6040         bp = &boards[moveNum][i][left];
6041         for (j = left; j < right; j++, bp++) {
6042           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6043           if ((int) *bp < (int) BlackPawn) {
6044             if(j == BOARD_RGHT+1)
6045                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6046             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6047             if(message[0] == '+' || message[0] == '~') {
6048               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6049                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6050                         AAA + j, ONE + i);
6051             }
6052             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6053                 message[1] = BOARD_RGHT   - 1 - j + '1';
6054                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6055             }
6056             SendToProgram(message, cps);
6057           }
6058         }
6059       }
6060
6061       SendToProgram("c\n", cps);
6062       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6063         bp = &boards[moveNum][i][left];
6064         for (j = left; j < right; j++, bp++) {
6065           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6066           if (((int) *bp != (int) EmptySquare)
6067               && ((int) *bp >= (int) BlackPawn)) {
6068             if(j == BOARD_LEFT-2)
6069                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6070             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6071                     AAA + j, ONE + i);
6072             if(message[0] == '+' || message[0] == '~') {
6073               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6074                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6075                         AAA + j, ONE + i);
6076             }
6077             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6078                 message[1] = BOARD_RGHT   - 1 - j + '1';
6079                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6080             }
6081             SendToProgram(message, cps);
6082           }
6083         }
6084       }
6085
6086       SendToProgram(".\n", cps);
6087     }
6088     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6089 }
6090
6091 ChessSquare
6092 DefaultPromoChoice (int white)
6093 {
6094     ChessSquare result;
6095     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6096         result = WhiteFerz; // no choice
6097     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6098         result= WhiteKing; // in Suicide Q is the last thing we want
6099     else if(gameInfo.variant == VariantSpartan)
6100         result = white ? WhiteQueen : WhiteAngel;
6101     else result = WhiteQueen;
6102     if(!white) result = WHITE_TO_BLACK result;
6103     return result;
6104 }
6105
6106 static int autoQueen; // [HGM] oneclick
6107
6108 int
6109 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6110 {
6111     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6112     /* [HGM] add Shogi promotions */
6113     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6114     ChessSquare piece;
6115     ChessMove moveType;
6116     Boolean premove;
6117
6118     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6119     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6120
6121     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6122       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6123         return FALSE;
6124
6125     piece = boards[currentMove][fromY][fromX];
6126     if(gameInfo.variant == VariantShogi) {
6127         promotionZoneSize = BOARD_HEIGHT/3;
6128         highestPromotingPiece = (int)WhiteFerz;
6129     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6130         promotionZoneSize = 3;
6131     }
6132
6133     // Treat Lance as Pawn when it is not representing Amazon
6134     if(gameInfo.variant != VariantSuper) {
6135         if(piece == WhiteLance) piece = WhitePawn; else
6136         if(piece == BlackLance) piece = BlackPawn;
6137     }
6138
6139     // next weed out all moves that do not touch the promotion zone at all
6140     if((int)piece >= BlackPawn) {
6141         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6142              return FALSE;
6143         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6144     } else {
6145         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6146            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6147     }
6148
6149     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6150
6151     // weed out mandatory Shogi promotions
6152     if(gameInfo.variant == VariantShogi) {
6153         if(piece >= BlackPawn) {
6154             if(toY == 0 && piece == BlackPawn ||
6155                toY == 0 && piece == BlackQueen ||
6156                toY <= 1 && piece == BlackKnight) {
6157                 *promoChoice = '+';
6158                 return FALSE;
6159             }
6160         } else {
6161             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6162                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6163                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6164                 *promoChoice = '+';
6165                 return FALSE;
6166             }
6167         }
6168     }
6169
6170     // weed out obviously illegal Pawn moves
6171     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6172         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6173         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6174         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6175         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6176         // note we are not allowed to test for valid (non-)capture, due to premove
6177     }
6178
6179     // we either have a choice what to promote to, or (in Shogi) whether to promote
6180     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6181         *promoChoice = PieceToChar(BlackFerz);  // no choice
6182         return FALSE;
6183     }
6184     // no sense asking what we must promote to if it is going to explode...
6185     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6186         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6187         return FALSE;
6188     }
6189     // give caller the default choice even if we will not make it
6190     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6191     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6192     if(        sweepSelect && gameInfo.variant != VariantGreat
6193                            && gameInfo.variant != VariantGrand
6194                            && gameInfo.variant != VariantSuper) return FALSE;
6195     if(autoQueen) return FALSE; // predetermined
6196
6197     // suppress promotion popup on illegal moves that are not premoves
6198     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6199               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6200     if(appData.testLegality && !premove) {
6201         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6202                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6203         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6204             return FALSE;
6205     }
6206
6207     return TRUE;
6208 }
6209
6210 int
6211 InPalace (int row, int column)
6212 {   /* [HGM] for Xiangqi */
6213     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6214          column < (BOARD_WIDTH + 4)/2 &&
6215          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6216     return FALSE;
6217 }
6218
6219 int
6220 PieceForSquare (int x, int y)
6221 {
6222   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6223      return -1;
6224   else
6225      return boards[currentMove][y][x];
6226 }
6227
6228 int
6229 OKToStartUserMove (int x, int y)
6230 {
6231     ChessSquare from_piece;
6232     int white_piece;
6233
6234     if (matchMode) return FALSE;
6235     if (gameMode == EditPosition) return TRUE;
6236
6237     if (x >= 0 && y >= 0)
6238       from_piece = boards[currentMove][y][x];
6239     else
6240       from_piece = EmptySquare;
6241
6242     if (from_piece == EmptySquare) return FALSE;
6243
6244     white_piece = (int)from_piece >= (int)WhitePawn &&
6245       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6246
6247     switch (gameMode) {
6248       case AnalyzeFile:
6249       case TwoMachinesPlay:
6250       case EndOfGame:
6251         return FALSE;
6252
6253       case IcsObserving:
6254       case IcsIdle:
6255         return FALSE;
6256
6257       case MachinePlaysWhite:
6258       case IcsPlayingBlack:
6259         if (appData.zippyPlay) return FALSE;
6260         if (white_piece) {
6261             DisplayMoveError(_("You are playing Black"));
6262             return FALSE;
6263         }
6264         break;
6265
6266       case MachinePlaysBlack:
6267       case IcsPlayingWhite:
6268         if (appData.zippyPlay) return FALSE;
6269         if (!white_piece) {
6270             DisplayMoveError(_("You are playing White"));
6271             return FALSE;
6272         }
6273         break;
6274
6275       case PlayFromGameFile:
6276             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6277       case EditGame:
6278         if (!white_piece && WhiteOnMove(currentMove)) {
6279             DisplayMoveError(_("It is White's turn"));
6280             return FALSE;
6281         }
6282         if (white_piece && !WhiteOnMove(currentMove)) {
6283             DisplayMoveError(_("It is Black's turn"));
6284             return FALSE;
6285         }
6286         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6287             /* Editing correspondence game history */
6288             /* Could disallow this or prompt for confirmation */
6289             cmailOldMove = -1;
6290         }
6291         break;
6292
6293       case BeginningOfGame:
6294         if (appData.icsActive) return FALSE;
6295         if (!appData.noChessProgram) {
6296             if (!white_piece) {
6297                 DisplayMoveError(_("You are playing White"));
6298                 return FALSE;
6299             }
6300         }
6301         break;
6302
6303       case Training:
6304         if (!white_piece && WhiteOnMove(currentMove)) {
6305             DisplayMoveError(_("It is White's turn"));
6306             return FALSE;
6307         }
6308         if (white_piece && !WhiteOnMove(currentMove)) {
6309             DisplayMoveError(_("It is Black's turn"));
6310             return FALSE;
6311         }
6312         break;
6313
6314       default:
6315       case IcsExamining:
6316         break;
6317     }
6318     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6319         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6320         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6321         && gameMode != AnalyzeFile && gameMode != Training) {
6322         DisplayMoveError(_("Displayed position is not current"));
6323         return FALSE;
6324     }
6325     return TRUE;
6326 }
6327
6328 Boolean
6329 OnlyMove (int *x, int *y, Boolean captures) 
6330 {
6331     DisambiguateClosure cl;
6332     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6333     switch(gameMode) {
6334       case MachinePlaysBlack:
6335       case IcsPlayingWhite:
6336       case BeginningOfGame:
6337         if(!WhiteOnMove(currentMove)) return FALSE;
6338         break;
6339       case MachinePlaysWhite:
6340       case IcsPlayingBlack:
6341         if(WhiteOnMove(currentMove)) return FALSE;
6342         break;
6343       case EditGame:
6344         break;
6345       default:
6346         return FALSE;
6347     }
6348     cl.pieceIn = EmptySquare;
6349     cl.rfIn = *y;
6350     cl.ffIn = *x;
6351     cl.rtIn = -1;
6352     cl.ftIn = -1;
6353     cl.promoCharIn = NULLCHAR;
6354     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6355     if( cl.kind == NormalMove ||
6356         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6357         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6358         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6359       fromX = cl.ff;
6360       fromY = cl.rf;
6361       *x = cl.ft;
6362       *y = cl.rt;
6363       return TRUE;
6364     }
6365     if(cl.kind != ImpossibleMove) return FALSE;
6366     cl.pieceIn = EmptySquare;
6367     cl.rfIn = -1;
6368     cl.ffIn = -1;
6369     cl.rtIn = *y;
6370     cl.ftIn = *x;
6371     cl.promoCharIn = NULLCHAR;
6372     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6373     if( cl.kind == NormalMove ||
6374         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6375         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6376         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6377       fromX = cl.ff;
6378       fromY = cl.rf;
6379       *x = cl.ft;
6380       *y = cl.rt;
6381       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6382       return TRUE;
6383     }
6384     return FALSE;
6385 }
6386
6387 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6388 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6389 int lastLoadGameUseList = FALSE;
6390 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6391 ChessMove lastLoadGameStart = EndOfFile;
6392
6393 void
6394 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6395 {
6396     ChessMove moveType;
6397     ChessSquare pdown, pup;
6398
6399     /* Check if the user is playing in turn.  This is complicated because we
6400        let the user "pick up" a piece before it is his turn.  So the piece he
6401        tried to pick up may have been captured by the time he puts it down!
6402        Therefore we use the color the user is supposed to be playing in this
6403        test, not the color of the piece that is currently on the starting
6404        square---except in EditGame mode, where the user is playing both
6405        sides; fortunately there the capture race can't happen.  (It can
6406        now happen in IcsExamining mode, but that's just too bad.  The user
6407        will get a somewhat confusing message in that case.)
6408        */
6409
6410     switch (gameMode) {
6411       case AnalyzeFile:
6412       case TwoMachinesPlay:
6413       case EndOfGame:
6414       case IcsObserving:
6415       case IcsIdle:
6416         /* We switched into a game mode where moves are not accepted,
6417            perhaps while the mouse button was down. */
6418         return;
6419
6420       case MachinePlaysWhite:
6421         /* User is moving for Black */
6422         if (WhiteOnMove(currentMove)) {
6423             DisplayMoveError(_("It is White's turn"));
6424             return;
6425         }
6426         break;
6427
6428       case MachinePlaysBlack:
6429         /* User is moving for White */
6430         if (!WhiteOnMove(currentMove)) {
6431             DisplayMoveError(_("It is Black's turn"));
6432             return;
6433         }
6434         break;
6435
6436       case PlayFromGameFile:
6437             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6438       case EditGame:
6439       case IcsExamining:
6440       case BeginningOfGame:
6441       case AnalyzeMode:
6442       case Training:
6443         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6444         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6445             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6446             /* User is moving for Black */
6447             if (WhiteOnMove(currentMove)) {
6448                 DisplayMoveError(_("It is White's turn"));
6449                 return;
6450             }
6451         } else {
6452             /* User is moving for White */
6453             if (!WhiteOnMove(currentMove)) {
6454                 DisplayMoveError(_("It is Black's turn"));
6455                 return;
6456             }
6457         }
6458         break;
6459
6460       case IcsPlayingBlack:
6461         /* User is moving for Black */
6462         if (WhiteOnMove(currentMove)) {
6463             if (!appData.premove) {
6464                 DisplayMoveError(_("It is White's turn"));
6465             } else if (toX >= 0 && toY >= 0) {
6466                 premoveToX = toX;
6467                 premoveToY = toY;
6468                 premoveFromX = fromX;
6469                 premoveFromY = fromY;
6470                 premovePromoChar = promoChar;
6471                 gotPremove = 1;
6472                 if (appData.debugMode)
6473                     fprintf(debugFP, "Got premove: fromX %d,"
6474                             "fromY %d, toX %d, toY %d\n",
6475                             fromX, fromY, toX, toY);
6476             }
6477             return;
6478         }
6479         break;
6480
6481       case IcsPlayingWhite:
6482         /* User is moving for White */
6483         if (!WhiteOnMove(currentMove)) {
6484             if (!appData.premove) {
6485                 DisplayMoveError(_("It is Black's turn"));
6486             } else if (toX >= 0 && toY >= 0) {
6487                 premoveToX = toX;
6488                 premoveToY = toY;
6489                 premoveFromX = fromX;
6490                 premoveFromY = fromY;
6491                 premovePromoChar = promoChar;
6492                 gotPremove = 1;
6493                 if (appData.debugMode)
6494                     fprintf(debugFP, "Got premove: fromX %d,"
6495                             "fromY %d, toX %d, toY %d\n",
6496                             fromX, fromY, toX, toY);
6497             }
6498             return;
6499         }
6500         break;
6501
6502       default:
6503         break;
6504
6505       case EditPosition:
6506         /* EditPosition, empty square, or different color piece;
6507            click-click move is possible */
6508         if (toX == -2 || toY == -2) {
6509             boards[0][fromY][fromX] = EmptySquare;
6510             DrawPosition(FALSE, boards[currentMove]);
6511             return;
6512         } else if (toX >= 0 && toY >= 0) {
6513             boards[0][toY][toX] = boards[0][fromY][fromX];
6514             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6515                 if(boards[0][fromY][0] != EmptySquare) {
6516                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6517                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6518                 }
6519             } else
6520             if(fromX == BOARD_RGHT+1) {
6521                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6522                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6523                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6524                 }
6525             } else
6526             boards[0][fromY][fromX] = EmptySquare;
6527             DrawPosition(FALSE, boards[currentMove]);
6528             return;
6529         }
6530         return;
6531     }
6532
6533     if(toX < 0 || toY < 0) return;
6534     pdown = boards[currentMove][fromY][fromX];
6535     pup = boards[currentMove][toY][toX];
6536
6537     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6538     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6539          if( pup != EmptySquare ) return;
6540          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6541            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6542                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6543            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6544            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6545            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6546            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6547          fromY = DROP_RANK;
6548     }
6549
6550     /* [HGM] always test for legality, to get promotion info */
6551     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6552                                          fromY, fromX, toY, toX, promoChar);
6553
6554     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6555
6556     /* [HGM] but possibly ignore an IllegalMove result */
6557     if (appData.testLegality) {
6558         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6559             DisplayMoveError(_("Illegal move"));
6560             return;
6561         }
6562     }
6563
6564     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6565 }
6566
6567 /* Common tail of UserMoveEvent and DropMenuEvent */
6568 int
6569 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6570 {
6571     char *bookHit = 0;
6572
6573     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6574         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6575         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6576         if(WhiteOnMove(currentMove)) {
6577             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6578         } else {
6579             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6580         }
6581     }
6582
6583     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6584        move type in caller when we know the move is a legal promotion */
6585     if(moveType == NormalMove && promoChar)
6586         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6587
6588     /* [HGM] <popupFix> The following if has been moved here from
6589        UserMoveEvent(). Because it seemed to belong here (why not allow
6590        piece drops in training games?), and because it can only be
6591        performed after it is known to what we promote. */
6592     if (gameMode == Training) {
6593       /* compare the move played on the board to the next move in the
6594        * game. If they match, display the move and the opponent's response.
6595        * If they don't match, display an error message.
6596        */
6597       int saveAnimate;
6598       Board testBoard;
6599       CopyBoard(testBoard, boards[currentMove]);
6600       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6601
6602       if (CompareBoards(testBoard, boards[currentMove+1])) {
6603         ForwardInner(currentMove+1);
6604
6605         /* Autoplay the opponent's response.
6606          * if appData.animate was TRUE when Training mode was entered,
6607          * the response will be animated.
6608          */
6609         saveAnimate = appData.animate;
6610         appData.animate = animateTraining;
6611         ForwardInner(currentMove+1);
6612         appData.animate = saveAnimate;
6613
6614         /* check for the end of the game */
6615         if (currentMove >= forwardMostMove) {
6616           gameMode = PlayFromGameFile;
6617           ModeHighlight();
6618           SetTrainingModeOff();
6619           DisplayInformation(_("End of game"));
6620         }
6621       } else {
6622         DisplayError(_("Incorrect move"), 0);
6623       }
6624       return 1;
6625     }
6626
6627   /* Ok, now we know that the move is good, so we can kill
6628      the previous line in Analysis Mode */
6629   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6630                                 && currentMove < forwardMostMove) {
6631     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6632     else forwardMostMove = currentMove;
6633   }
6634
6635   /* If we need the chess program but it's dead, restart it */
6636   ResurrectChessProgram();
6637
6638   /* A user move restarts a paused game*/
6639   if (pausing)
6640     PauseEvent();
6641
6642   thinkOutput[0] = NULLCHAR;
6643
6644   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6645
6646   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6647     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6648     return 1;
6649   }
6650
6651   if (gameMode == BeginningOfGame) {
6652     if (appData.noChessProgram) {
6653       gameMode = EditGame;
6654       SetGameInfo();
6655     } else {
6656       char buf[MSG_SIZ];
6657       gameMode = MachinePlaysBlack;
6658       StartClocks();
6659       SetGameInfo();
6660       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6661       DisplayTitle(buf);
6662       if (first.sendName) {
6663         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6664         SendToProgram(buf, &first);
6665       }
6666       StartClocks();
6667     }
6668     ModeHighlight();
6669   }
6670
6671   /* Relay move to ICS or chess engine */
6672   if (appData.icsActive) {
6673     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6674         gameMode == IcsExamining) {
6675       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6676         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6677         SendToICS("draw ");
6678         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6679       }
6680       // also send plain move, in case ICS does not understand atomic claims
6681       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6682       ics_user_moved = 1;
6683     }
6684   } else {
6685     if (first.sendTime && (gameMode == BeginningOfGame ||
6686                            gameMode == MachinePlaysWhite ||
6687                            gameMode == MachinePlaysBlack)) {
6688       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6689     }
6690     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6691          // [HGM] book: if program might be playing, let it use book
6692         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6693         first.maybeThinking = TRUE;
6694     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6695         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6696         SendBoard(&first, currentMove+1);
6697     } else SendMoveToProgram(forwardMostMove-1, &first);
6698     if (currentMove == cmailOldMove + 1) {
6699       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6700     }
6701   }
6702
6703   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6704
6705   switch (gameMode) {
6706   case EditGame:
6707     if(appData.testLegality)
6708     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6709     case MT_NONE:
6710     case MT_CHECK:
6711       break;
6712     case MT_CHECKMATE:
6713     case MT_STAINMATE:
6714       if (WhiteOnMove(currentMove)) {
6715         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6716       } else {
6717         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6718       }
6719       break;
6720     case MT_STALEMATE:
6721       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6722       break;
6723     }
6724     break;
6725
6726   case MachinePlaysBlack:
6727   case MachinePlaysWhite:
6728     /* disable certain menu options while machine is thinking */
6729     SetMachineThinkingEnables();
6730     break;
6731
6732   default:
6733     break;
6734   }
6735
6736   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6737   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6738
6739   if(bookHit) { // [HGM] book: simulate book reply
6740         static char bookMove[MSG_SIZ]; // a bit generous?
6741
6742         programStats.nodes = programStats.depth = programStats.time =
6743         programStats.score = programStats.got_only_move = 0;
6744         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6745
6746         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6747         strcat(bookMove, bookHit);
6748         HandleMachineMove(bookMove, &first);
6749   }
6750   return 1;
6751 }
6752
6753 void
6754 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6755 {
6756     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6757     Markers *m = (Markers *) closure;
6758     if(rf == fromY && ff == fromX)
6759         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6760                          || kind == WhiteCapturesEnPassant
6761                          || kind == BlackCapturesEnPassant);
6762     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6763 }
6764
6765 void
6766 MarkTargetSquares (int clear)
6767 {
6768   int x, y;
6769   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6770      !appData.testLegality || gameMode == EditPosition) return;
6771   if(clear) {
6772     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6773   } else {
6774     int capt = 0;
6775     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6776     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6777       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6778       if(capt)
6779       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6780     }
6781   }
6782   DrawPosition(TRUE, NULL);
6783 }
6784
6785 int
6786 Explode (Board board, int fromX, int fromY, int toX, int toY)
6787 {
6788     if(gameInfo.variant == VariantAtomic &&
6789        (board[toY][toX] != EmptySquare ||                     // capture?
6790         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6791                          board[fromY][fromX] == BlackPawn   )
6792       )) {
6793         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6794         return TRUE;
6795     }
6796     return FALSE;
6797 }
6798
6799 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6800
6801 int
6802 CanPromote (ChessSquare piece, int y)
6803 {
6804         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6805         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6806         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6807            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6808            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6809                                                   gameInfo.variant == VariantMakruk) return FALSE;
6810         return (piece == BlackPawn && y == 1 ||
6811                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6812                 piece == BlackLance && y == 1 ||
6813                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6814 }
6815
6816 void
6817 LeftClick (ClickType clickType, int xPix, int yPix)
6818 {
6819     int x, y;
6820     Boolean saveAnimate;
6821     static int second = 0, promotionChoice = 0, clearFlag = 0;
6822     char promoChoice = NULLCHAR;
6823     ChessSquare piece;
6824
6825     if(appData.seekGraph && appData.icsActive && loggedOn &&
6826         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6827         SeekGraphClick(clickType, xPix, yPix, 0);
6828         return;
6829     }
6830
6831     if (clickType == Press) ErrorPopDown();
6832
6833     x = EventToSquare(xPix, BOARD_WIDTH);
6834     y = EventToSquare(yPix, BOARD_HEIGHT);
6835     if (!flipView && y >= 0) {
6836         y = BOARD_HEIGHT - 1 - y;
6837     }
6838     if (flipView && x >= 0) {
6839         x = BOARD_WIDTH - 1 - x;
6840     }
6841
6842     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6843         defaultPromoChoice = promoSweep;
6844         promoSweep = EmptySquare;   // terminate sweep
6845         promoDefaultAltered = TRUE;
6846         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6847     }
6848
6849     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6850         if(clickType == Release) return; // ignore upclick of click-click destination
6851         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6852         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6853         if(gameInfo.holdingsWidth &&
6854                 (WhiteOnMove(currentMove)
6855                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6856                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6857             // click in right holdings, for determining promotion piece
6858             ChessSquare p = boards[currentMove][y][x];
6859             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6860             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6861             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6862                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6863                 fromX = fromY = -1;
6864                 return;
6865             }
6866         }
6867         DrawPosition(FALSE, boards[currentMove]);
6868         return;
6869     }
6870
6871     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6872     if(clickType == Press
6873             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6874               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6875               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6876         return;
6877
6878     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6879         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6880
6881     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6882         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6883                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6884         defaultPromoChoice = DefaultPromoChoice(side);
6885     }
6886
6887     autoQueen = appData.alwaysPromoteToQueen;
6888
6889     if (fromX == -1) {
6890       int originalY = y;
6891       gatingPiece = EmptySquare;
6892       if (clickType != Press) {
6893         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6894             DragPieceEnd(xPix, yPix); dragging = 0;
6895             DrawPosition(FALSE, NULL);
6896         }
6897         return;
6898       }
6899       fromX = x; fromY = y; toX = toY = -1;
6900       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6901          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6902          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6903             /* First square */
6904             if (OKToStartUserMove(fromX, fromY)) {
6905                 second = 0;
6906                 MarkTargetSquares(0);
6907                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6908                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6909                     promoSweep = defaultPromoChoice;
6910                     selectFlag = 0; lastX = xPix; lastY = yPix;
6911                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6912                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6913                 }
6914                 if (appData.highlightDragging) {
6915                     SetHighlights(fromX, fromY, -1, -1);
6916                 }
6917             } else fromX = fromY = -1;
6918             return;
6919         }
6920     }
6921
6922     /* fromX != -1 */
6923     if (clickType == Press && gameMode != EditPosition) {
6924         ChessSquare fromP;
6925         ChessSquare toP;
6926         int frc;
6927
6928         // ignore off-board to clicks
6929         if(y < 0 || x < 0) return;
6930
6931         /* Check if clicking again on the same color piece */
6932         fromP = boards[currentMove][fromY][fromX];
6933         toP = boards[currentMove][y][x];
6934         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6935         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6936              WhitePawn <= toP && toP <= WhiteKing &&
6937              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6938              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6939             (BlackPawn <= fromP && fromP <= BlackKing &&
6940              BlackPawn <= toP && toP <= BlackKing &&
6941              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6942              !(fromP == BlackKing && toP == BlackRook && frc))) {
6943             /* Clicked again on same color piece -- changed his mind */
6944             second = (x == fromX && y == fromY);
6945             promoDefaultAltered = FALSE;
6946             MarkTargetSquares(1);
6947            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6948             if (appData.highlightDragging) {
6949                 SetHighlights(x, y, -1, -1);
6950             } else {
6951                 ClearHighlights();
6952             }
6953             if (OKToStartUserMove(x, y)) {
6954                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6955                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6956                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6957                  gatingPiece = boards[currentMove][fromY][fromX];
6958                 else gatingPiece = EmptySquare;
6959                 fromX = x;
6960                 fromY = y; dragging = 1;
6961                 MarkTargetSquares(0);
6962                 DragPieceBegin(xPix, yPix, FALSE);
6963                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6964                     promoSweep = defaultPromoChoice;
6965                     selectFlag = 0; lastX = xPix; lastY = yPix;
6966                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6967                 }
6968             }
6969            }
6970            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6971            second = FALSE; 
6972         }
6973         // ignore clicks on holdings
6974         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6975     }
6976
6977     if (clickType == Release && x == fromX && y == fromY) {
6978         DragPieceEnd(xPix, yPix); dragging = 0;
6979         if(clearFlag) {
6980             // a deferred attempt to click-click move an empty square on top of a piece
6981             boards[currentMove][y][x] = EmptySquare;
6982             ClearHighlights();
6983             DrawPosition(FALSE, boards[currentMove]);
6984             fromX = fromY = -1; clearFlag = 0;
6985             return;
6986         }
6987         if (appData.animateDragging) {
6988             /* Undo animation damage if any */
6989             DrawPosition(FALSE, NULL);
6990         }
6991         if (second) {
6992             /* Second up/down in same square; just abort move */
6993             second = 0;
6994             fromX = fromY = -1;
6995             gatingPiece = EmptySquare;
6996             ClearHighlights();
6997             gotPremove = 0;
6998             ClearPremoveHighlights();
6999         } else {
7000             /* First upclick in same square; start click-click mode */
7001             SetHighlights(x, y, -1, -1);
7002         }
7003         return;
7004     }
7005
7006     clearFlag = 0;
7007
7008     /* we now have a different from- and (possibly off-board) to-square */
7009     /* Completed move */
7010     toX = x;
7011     toY = y;
7012     saveAnimate = appData.animate;
7013     MarkTargetSquares(1);
7014     if (clickType == Press) {
7015         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7016             // must be Edit Position mode with empty-square selected
7017             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7018             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7019             return;
7020         }
7021         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7022             ChessSquare piece = boards[currentMove][fromY][fromX];
7023             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7024             promoSweep = defaultPromoChoice;
7025             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7026             selectFlag = 0; lastX = xPix; lastY = yPix;
7027             Sweep(0); // Pawn that is going to promote: preview promotion piece
7028             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7029             DrawPosition(FALSE, boards[currentMove]);
7030             return;
7031         }
7032         /* Finish clickclick move */
7033         if (appData.animate || appData.highlightLastMove) {
7034             SetHighlights(fromX, fromY, toX, toY);
7035         } else {
7036             ClearHighlights();
7037         }
7038     } else {
7039         /* Finish drag move */
7040         if (appData.highlightLastMove) {
7041             SetHighlights(fromX, fromY, toX, toY);
7042         } else {
7043             ClearHighlights();
7044         }
7045         DragPieceEnd(xPix, yPix); dragging = 0;
7046         /* Don't animate move and drag both */
7047         appData.animate = FALSE;
7048     }
7049
7050     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7051     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7052         ChessSquare piece = boards[currentMove][fromY][fromX];
7053         if(gameMode == EditPosition && piece != EmptySquare &&
7054            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7055             int n;
7056
7057             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7058                 n = PieceToNumber(piece - (int)BlackPawn);
7059                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7060                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7061                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7062             } else
7063             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7064                 n = PieceToNumber(piece);
7065                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7066                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7067                 boards[currentMove][n][BOARD_WIDTH-2]++;
7068             }
7069             boards[currentMove][fromY][fromX] = EmptySquare;
7070         }
7071         ClearHighlights();
7072         fromX = fromY = -1;
7073         DrawPosition(TRUE, boards[currentMove]);
7074         return;
7075     }
7076
7077     // off-board moves should not be highlighted
7078     if(x < 0 || y < 0) ClearHighlights();
7079
7080     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7081
7082     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7083         SetHighlights(fromX, fromY, toX, toY);
7084         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7085             // [HGM] super: promotion to captured piece selected from holdings
7086             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7087             promotionChoice = TRUE;
7088             // kludge follows to temporarily execute move on display, without promoting yet
7089             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7090             boards[currentMove][toY][toX] = p;
7091             DrawPosition(FALSE, boards[currentMove]);
7092             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7093             boards[currentMove][toY][toX] = q;
7094             DisplayMessage("Click in holdings to choose piece", "");
7095             return;
7096         }
7097         PromotionPopUp();
7098     } else {
7099         int oldMove = currentMove;
7100         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7101         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7102         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7103         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7104            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7105             DrawPosition(TRUE, boards[currentMove]);
7106         fromX = fromY = -1;
7107     }
7108     appData.animate = saveAnimate;
7109     if (appData.animate || appData.animateDragging) {
7110         /* Undo animation damage if needed */
7111         DrawPosition(FALSE, NULL);
7112     }
7113 }
7114
7115 int
7116 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7117 {   // front-end-free part taken out of PieceMenuPopup
7118     int whichMenu; int xSqr, ySqr;
7119
7120     if(seekGraphUp) { // [HGM] seekgraph
7121         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7122         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7123         return -2;
7124     }
7125
7126     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7127          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7128         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7129         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7130         if(action == Press)   {
7131             originalFlip = flipView;
7132             flipView = !flipView; // temporarily flip board to see game from partners perspective
7133             DrawPosition(TRUE, partnerBoard);
7134             DisplayMessage(partnerStatus, "");
7135             partnerUp = TRUE;
7136         } else if(action == Release) {
7137             flipView = originalFlip;
7138             DrawPosition(TRUE, boards[currentMove]);
7139             partnerUp = FALSE;
7140         }
7141         return -2;
7142     }
7143
7144     xSqr = EventToSquare(x, BOARD_WIDTH);
7145     ySqr = EventToSquare(y, BOARD_HEIGHT);
7146     if (action == Release) {
7147         if(pieceSweep != EmptySquare) {
7148             EditPositionMenuEvent(pieceSweep, toX, toY);
7149             pieceSweep = EmptySquare;
7150         } else UnLoadPV(); // [HGM] pv
7151     }
7152     if (action != Press) return -2; // return code to be ignored
7153     switch (gameMode) {
7154       case IcsExamining:
7155         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7156       case EditPosition:
7157         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7158         if (xSqr < 0 || ySqr < 0) return -1;
7159         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7160         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7161         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7162         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7163         NextPiece(0);
7164         return 2; // grab
7165       case IcsObserving:
7166         if(!appData.icsEngineAnalyze) return -1;
7167       case IcsPlayingWhite:
7168       case IcsPlayingBlack:
7169         if(!appData.zippyPlay) goto noZip;
7170       case AnalyzeMode:
7171       case AnalyzeFile:
7172       case MachinePlaysWhite:
7173       case MachinePlaysBlack:
7174       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7175         if (!appData.dropMenu) {
7176           LoadPV(x, y);
7177           return 2; // flag front-end to grab mouse events
7178         }
7179         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7180            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7181       case EditGame:
7182       noZip:
7183         if (xSqr < 0 || ySqr < 0) return -1;
7184         if (!appData.dropMenu || appData.testLegality &&
7185             gameInfo.variant != VariantBughouse &&
7186             gameInfo.variant != VariantCrazyhouse) return -1;
7187         whichMenu = 1; // drop menu
7188         break;
7189       default:
7190         return -1;
7191     }
7192
7193     if (((*fromX = xSqr) < 0) ||
7194         ((*fromY = ySqr) < 0)) {
7195         *fromX = *fromY = -1;
7196         return -1;
7197     }
7198     if (flipView)
7199       *fromX = BOARD_WIDTH - 1 - *fromX;
7200     else
7201       *fromY = BOARD_HEIGHT - 1 - *fromY;
7202
7203     return whichMenu;
7204 }
7205
7206 void
7207 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7208 {
7209 //    char * hint = lastHint;
7210     FrontEndProgramStats stats;
7211
7212     stats.which = cps == &first ? 0 : 1;
7213     stats.depth = cpstats->depth;
7214     stats.nodes = cpstats->nodes;
7215     stats.score = cpstats->score;
7216     stats.time = cpstats->time;
7217     stats.pv = cpstats->movelist;
7218     stats.hint = lastHint;
7219     stats.an_move_index = 0;
7220     stats.an_move_count = 0;
7221
7222     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7223         stats.hint = cpstats->move_name;
7224         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7225         stats.an_move_count = cpstats->nr_moves;
7226     }
7227
7228     if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each
7229
7230     SetProgramStats( &stats );
7231 }
7232
7233 void
7234 ClearEngineOutputPane (int which)
7235 {
7236     static FrontEndProgramStats dummyStats;
7237     dummyStats.which = which;
7238     dummyStats.pv = "#";
7239     SetProgramStats( &dummyStats );
7240 }
7241
7242 #define MAXPLAYERS 500
7243
7244 char *
7245 TourneyStandings (int display)
7246 {
7247     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7248     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7249     char result, *p, *names[MAXPLAYERS];
7250
7251     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7252         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7253     names[0] = p = strdup(appData.participants);
7254     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7255
7256     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7257
7258     while(result = appData.results[nr]) {
7259         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7260         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7261         wScore = bScore = 0;
7262         switch(result) {
7263           case '+': wScore = 2; break;
7264           case '-': bScore = 2; break;
7265           case '=': wScore = bScore = 1; break;
7266           case ' ':
7267           case '*': return strdup("busy"); // tourney not finished
7268         }
7269         score[w] += wScore;
7270         score[b] += bScore;
7271         games[w]++;
7272         games[b]++;
7273         nr++;
7274     }
7275     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7276     for(w=0; w<nPlayers; w++) {
7277         bScore = -1;
7278         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7279         ranking[w] = b; points[w] = bScore; score[b] = -2;
7280     }
7281     p = malloc(nPlayers*34+1);
7282     for(w=0; w<nPlayers && w<display; w++)
7283         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7284     free(names[0]);
7285     return p;
7286 }
7287
7288 void
7289 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7290 {       // count all piece types
7291         int p, f, r;
7292         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7293         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7294         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7295                 p = board[r][f];
7296                 pCnt[p]++;
7297                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7298                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7299                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7300                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7301                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7302                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7303         }
7304 }
7305
7306 int
7307 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7308 {
7309         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7310         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7311
7312         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7313         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7314         if(myPawns == 2 && nMine == 3) // KPP
7315             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7316         if(myPawns == 1 && nMine == 2) // KP
7317             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7318         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7319             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7320         if(myPawns) return FALSE;
7321         if(pCnt[WhiteRook+side])
7322             return pCnt[BlackRook-side] ||
7323                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7324                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7325                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7326         if(pCnt[WhiteCannon+side]) {
7327             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7328             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7329         }
7330         if(pCnt[WhiteKnight+side])
7331             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7332         return FALSE;
7333 }
7334
7335 int
7336 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7337 {
7338         VariantClass v = gameInfo.variant;
7339
7340         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7341         if(v == VariantShatranj) return TRUE; // always winnable through baring
7342         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7343         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7344
7345         if(v == VariantXiangqi) {
7346                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7347
7348                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7349                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7350                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7351                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7352                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7353                 if(stale) // we have at least one last-rank P plus perhaps C
7354                     return majors // KPKX
7355                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7356                 else // KCA*E*
7357                     return pCnt[WhiteFerz+side] // KCAK
7358                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7359                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7360                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7361
7362         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7363                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7364
7365                 if(nMine == 1) return FALSE; // bare King
7366                 if(nBishops && bisColor == 3) return TRUE; // There must be a second B/A/F, which can either block (his) or attack (mine) the escape square
7367                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7368                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7369                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7370                 if(pCnt[WhiteKnight+side])
7371                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7372                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7373                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7374                 if(nBishops)
7375                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7376                 if(pCnt[WhiteAlfil+side])
7377                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7378                 if(pCnt[WhiteWazir+side])
7379                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7380         }
7381
7382         return TRUE;
7383 }
7384
7385 int
7386 CompareWithRights (Board b1, Board b2)
7387 {
7388     int rights = 0;
7389     if(!CompareBoards(b1, b2)) return FALSE;
7390     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7391     /* compare castling rights */
7392     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7393            rights++; /* King lost rights, while rook still had them */
7394     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7395         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7396            rights++; /* but at least one rook lost them */
7397     }
7398     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7399            rights++;
7400     if( b1[CASTLING][5] != NoRights ) {
7401         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7402            rights++;
7403     }
7404     return rights == 0;
7405 }
7406
7407 int
7408 Adjudicate (ChessProgramState *cps)
7409 {       // [HGM] some adjudications useful with buggy engines
7410         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7411         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7412         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7413         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7414         int k, count = 0; static int bare = 1;
7415         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7416         Boolean canAdjudicate = !appData.icsActive;
7417
7418         // most tests only when we understand the game, i.e. legality-checking on
7419             if( appData.testLegality )
7420             {   /* [HGM] Some more adjudications for obstinate engines */
7421                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7422                 static int moveCount = 6;
7423                 ChessMove result;
7424                 char *reason = NULL;
7425
7426                 /* Count what is on board. */
7427                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7428
7429                 /* Some material-based adjudications that have to be made before stalemate test */
7430                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7431                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7432                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7433                      if(canAdjudicate && appData.checkMates) {
7434                          if(engineOpponent)
7435                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7436                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7437                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7438                          return 1;
7439                      }
7440                 }
7441
7442                 /* Bare King in Shatranj (loses) or Losers (wins) */
7443                 if( nrW == 1 || nrB == 1) {
7444                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7445                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7446                      if(canAdjudicate && appData.checkMates) {
7447                          if(engineOpponent)
7448                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7449                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7450                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7451                          return 1;
7452                      }
7453                   } else
7454                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7455                   {    /* bare King */
7456                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7457                         if(canAdjudicate && appData.checkMates) {
7458                             /* but only adjudicate if adjudication enabled */
7459                             if(engineOpponent)
7460                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7461                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7462                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7463                             return 1;
7464                         }
7465                   }
7466                 } else bare = 1;
7467
7468
7469             // don't wait for engine to announce game end if we can judge ourselves
7470             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7471               case MT_CHECK:
7472                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7473                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7474                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7475                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7476                             checkCnt++;
7477                         if(checkCnt >= 2) {
7478                             reason = "Xboard adjudication: 3rd check";
7479                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7480                             break;
7481                         }
7482                     }
7483                 }
7484               case MT_NONE:
7485               default:
7486                 break;
7487               case MT_STALEMATE:
7488               case MT_STAINMATE:
7489                 reason = "Xboard adjudication: Stalemate";
7490                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7491                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7492                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7493                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7494                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7495                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7496                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7497                                                                         EP_CHECKMATE : EP_WINS);
7498                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7499                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7500                 }
7501                 break;
7502               case MT_CHECKMATE:
7503                 reason = "Xboard adjudication: Checkmate";
7504                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7505                 break;
7506             }
7507
7508                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7509                     case EP_STALEMATE:
7510                         result = GameIsDrawn; break;
7511                     case EP_CHECKMATE:
7512                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7513                     case EP_WINS:
7514                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7515                     default:
7516                         result = EndOfFile;
7517                 }
7518                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7519                     if(engineOpponent)
7520                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7521                     GameEnds( result, reason, GE_XBOARD );
7522                     return 1;
7523                 }
7524
7525                 /* Next absolutely insufficient mating material. */
7526                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7527                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7528                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7529
7530                      /* always flag draws, for judging claims */
7531                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7532
7533                      if(canAdjudicate && appData.materialDraws) {
7534                          /* but only adjudicate them if adjudication enabled */
7535                          if(engineOpponent) {
7536                            SendToProgram("force\n", engineOpponent); // suppress reply
7537                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7538                          }
7539                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7540                          return 1;
7541                      }
7542                 }
7543
7544                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7545                 if(gameInfo.variant == VariantXiangqi ?
7546                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7547                  : nrW + nrB == 4 &&
7548                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7549                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7550                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7551                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7552                    ) ) {
7553                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7554                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7555                           if(engineOpponent) {
7556                             SendToProgram("force\n", engineOpponent); // suppress reply
7557                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7558                           }
7559                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7560                           return 1;
7561                      }
7562                 } else moveCount = 6;
7563             }
7564         if (appData.debugMode) { int i;
7565             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7566                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7567                     appData.drawRepeats);
7568             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7569               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7570
7571         }
7572
7573         // Repetition draws and 50-move rule can be applied independently of legality testing
7574
7575                 /* Check for rep-draws */
7576                 count = 0;
7577                 for(k = forwardMostMove-2;
7578                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7579                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7580                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7581                     k-=2)
7582                 {   int rights=0;
7583                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7584                         /* compare castling rights */
7585                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7586                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7587                                 rights++; /* King lost rights, while rook still had them */
7588                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7589                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7590                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7591                                    rights++; /* but at least one rook lost them */
7592                         }
7593                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7594                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7595                                 rights++;
7596                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7597                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7598                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7599                                    rights++;
7600                         }
7601                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7602                             && appData.drawRepeats > 1) {
7603                              /* adjudicate after user-specified nr of repeats */
7604                              int result = GameIsDrawn;
7605                              char *details = "XBoard adjudication: repetition draw";
7606                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7607                                 // [HGM] xiangqi: check for forbidden perpetuals
7608                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7609                                 for(m=forwardMostMove; m>k; m-=2) {
7610                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7611                                         ourPerpetual = 0; // the current mover did not always check
7612                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7613                                         hisPerpetual = 0; // the opponent did not always check
7614                                 }
7615                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7616                                                                         ourPerpetual, hisPerpetual);
7617                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7618                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7619                                     details = "Xboard adjudication: perpetual checking";
7620                                 } else
7621                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7622                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7623                                 } else
7624                                 // Now check for perpetual chases
7625                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7626                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7627                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7628                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7629                                         static char resdet[MSG_SIZ];
7630                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7631                                         details = resdet;
7632                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7633                                     } else
7634                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7635                                         break; // Abort repetition-checking loop.
7636                                 }
7637                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7638                              }
7639                              if(engineOpponent) {
7640                                SendToProgram("force\n", engineOpponent); // suppress reply
7641                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7642                              }
7643                              GameEnds( result, details, GE_XBOARD );
7644                              return 1;
7645                         }
7646                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7647                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7648                     }
7649                 }
7650
7651                 /* Now we test for 50-move draws. Determine ply count */
7652                 count = forwardMostMove;
7653                 /* look for last irreversble move */
7654                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7655                     count--;
7656                 /* if we hit starting position, add initial plies */
7657                 if( count == backwardMostMove )
7658                     count -= initialRulePlies;
7659                 count = forwardMostMove - count;
7660                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7661                         // adjust reversible move counter for checks in Xiangqi
7662                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7663                         if(i < backwardMostMove) i = backwardMostMove;
7664                         while(i <= forwardMostMove) {
7665                                 lastCheck = inCheck; // check evasion does not count
7666                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7667                                 if(inCheck || lastCheck) count--; // check does not count
7668                                 i++;
7669                         }
7670                 }
7671                 if( count >= 100)
7672                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7673                          /* this is used to judge if draw claims are legal */
7674                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7675                          if(engineOpponent) {
7676                            SendToProgram("force\n", engineOpponent); // suppress reply
7677                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7678                          }
7679                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7680                          return 1;
7681                 }
7682
7683                 /* if draw offer is pending, treat it as a draw claim
7684                  * when draw condition present, to allow engines a way to
7685                  * claim draws before making their move to avoid a race
7686                  * condition occurring after their move
7687                  */
7688                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7689                          char *p = NULL;
7690                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7691                              p = "Draw claim: 50-move rule";
7692                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7693                              p = "Draw claim: 3-fold repetition";
7694                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7695                              p = "Draw claim: insufficient mating material";
7696                          if( p != NULL && canAdjudicate) {
7697                              if(engineOpponent) {
7698                                SendToProgram("force\n", engineOpponent); // suppress reply
7699                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7700                              }
7701                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7702                              return 1;
7703                          }
7704                 }
7705
7706                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7707                     if(engineOpponent) {
7708                       SendToProgram("force\n", engineOpponent); // suppress reply
7709                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7710                     }
7711                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7712                     return 1;
7713                 }
7714         return 0;
7715 }
7716
7717 char *
7718 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7719 {   // [HGM] book: this routine intercepts moves to simulate book replies
7720     char *bookHit = NULL;
7721
7722     //first determine if the incoming move brings opponent into his book
7723     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7724         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7725     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7726     if(bookHit != NULL && !cps->bookSuspend) {
7727         // make sure opponent is not going to reply after receiving move to book position
7728         SendToProgram("force\n", cps);
7729         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7730     }
7731     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7732     // now arrange restart after book miss
7733     if(bookHit) {
7734         // after a book hit we never send 'go', and the code after the call to this routine
7735         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7736         char buf[MSG_SIZ], *move = bookHit;
7737         if(cps->useSAN) {
7738             int fromX, fromY, toX, toY;
7739             char promoChar;
7740             ChessMove moveType;
7741             move = buf + 30;
7742             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7743                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7744                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7745                                     PosFlags(forwardMostMove),
7746                                     fromY, fromX, toY, toX, promoChar, move);
7747             } else {
7748                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7749                 bookHit = NULL;
7750             }
7751         }
7752         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7753         SendToProgram(buf, cps);
7754         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7755     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7756         SendToProgram("go\n", cps);
7757         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7758     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7759         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7760             SendToProgram("go\n", cps);
7761         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7762     }
7763     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7764 }
7765
7766 char *savedMessage;
7767 ChessProgramState *savedState;
7768 void
7769 DeferredBookMove (void)
7770 {
7771         if(savedState->lastPing != savedState->lastPong)
7772                     ScheduleDelayedEvent(DeferredBookMove, 10);
7773         else
7774         HandleMachineMove(savedMessage, savedState);
7775 }
7776
7777 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7778
7779 void
7780 HandleMachineMove (char *message, ChessProgramState *cps)
7781 {
7782     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7783     char realname[MSG_SIZ];
7784     int fromX, fromY, toX, toY;
7785     ChessMove moveType;
7786     char promoChar;
7787     char *p, *pv=buf1;
7788     int machineWhite;
7789     char *bookHit;
7790
7791     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7792         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7793         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7794             DisplayError(_("Invalid pairing from pairing engine"), 0);
7795             return;
7796         }
7797         pairingReceived = 1;
7798         NextMatchGame();
7799         return; // Skim the pairing messages here.
7800     }
7801
7802     cps->userError = 0;
7803
7804 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7805     /*
7806      * Kludge to ignore BEL characters
7807      */
7808     while (*message == '\007') message++;
7809
7810     /*
7811      * [HGM] engine debug message: ignore lines starting with '#' character
7812      */
7813     if(cps->debug && *message == '#') return;
7814
7815     /*
7816      * Look for book output
7817      */
7818     if (cps == &first && bookRequested) {
7819         if (message[0] == '\t' || message[0] == ' ') {
7820             /* Part of the book output is here; append it */
7821             strcat(bookOutput, message);
7822             strcat(bookOutput, "  \n");
7823             return;
7824         } else if (bookOutput[0] != NULLCHAR) {
7825             /* All of book output has arrived; display it */
7826             char *p = bookOutput;
7827             while (*p != NULLCHAR) {
7828                 if (*p == '\t') *p = ' ';
7829                 p++;
7830             }
7831             DisplayInformation(bookOutput);
7832             bookRequested = FALSE;
7833             /* Fall through to parse the current output */
7834         }
7835     }
7836
7837     /*
7838      * Look for machine move.
7839      */
7840     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7841         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7842     {
7843         /* This method is only useful on engines that support ping */
7844         if (cps->lastPing != cps->lastPong) {
7845           if (gameMode == BeginningOfGame) {
7846             /* Extra move from before last new; ignore */
7847             if (appData.debugMode) {
7848                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7849             }
7850           } else {
7851             if (appData.debugMode) {
7852                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7853                         cps->which, gameMode);
7854             }
7855
7856             SendToProgram("undo\n", cps);
7857           }
7858           return;
7859         }
7860
7861         switch (gameMode) {
7862           case BeginningOfGame:
7863             /* Extra move from before last reset; ignore */
7864             if (appData.debugMode) {
7865                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7866             }
7867             return;
7868
7869           case EndOfGame:
7870           case IcsIdle:
7871           default:
7872             /* Extra move after we tried to stop.  The mode test is
7873                not a reliable way of detecting this problem, but it's
7874                the best we can do on engines that don't support ping.
7875             */
7876             if (appData.debugMode) {
7877                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7878                         cps->which, gameMode);
7879             }
7880             SendToProgram("undo\n", cps);
7881             return;
7882
7883           case MachinePlaysWhite:
7884           case IcsPlayingWhite:
7885             machineWhite = TRUE;
7886             break;
7887
7888           case MachinePlaysBlack:
7889           case IcsPlayingBlack:
7890             machineWhite = FALSE;
7891             break;
7892
7893           case TwoMachinesPlay:
7894             machineWhite = (cps->twoMachinesColor[0] == 'w');
7895             break;
7896         }
7897         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7898             if (appData.debugMode) {
7899                 fprintf(debugFP,
7900                         "Ignoring move out of turn by %s, gameMode %d"
7901                         ", forwardMost %d\n",
7902                         cps->which, gameMode, forwardMostMove);
7903             }
7904             return;
7905         }
7906
7907     if (appData.debugMode) { int f = forwardMostMove;
7908         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7909                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7910                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7911     }
7912         if(cps->alphaRank) AlphaRank(machineMove, 4);
7913         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7914                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7915             /* Machine move could not be parsed; ignore it. */
7916           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7917                     machineMove, _(cps->which));
7918             DisplayError(buf1, 0);
7919             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7920                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7921             if (gameMode == TwoMachinesPlay) {
7922               GameEnds(machineWhite ? BlackWins : WhiteWins,
7923                        buf1, GE_XBOARD);
7924             }
7925             return;
7926         }
7927
7928         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7929         /* So we have to redo legality test with true e.p. status here,  */
7930         /* to make sure an illegal e.p. capture does not slip through,   */
7931         /* to cause a forfeit on a justified illegal-move complaint      */
7932         /* of the opponent.                                              */
7933         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7934            ChessMove moveType;
7935            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7936                              fromY, fromX, toY, toX, promoChar);
7937             if (appData.debugMode) {
7938                 int i;
7939                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7940                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7941                 fprintf(debugFP, "castling rights\n");
7942             }
7943             if(moveType == IllegalMove) {
7944               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7945                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7946                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7947                            buf1, GE_XBOARD);
7948                 return;
7949            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7950            /* [HGM] Kludge to handle engines that send FRC-style castling
7951               when they shouldn't (like TSCP-Gothic) */
7952            switch(moveType) {
7953              case WhiteASideCastleFR:
7954              case BlackASideCastleFR:
7955                toX+=2;
7956                currentMoveString[2]++;
7957                break;
7958              case WhiteHSideCastleFR:
7959              case BlackHSideCastleFR:
7960                toX--;
7961                currentMoveString[2]--;
7962                break;
7963              default: ; // nothing to do, but suppresses warning of pedantic compilers
7964            }
7965         }
7966         hintRequested = FALSE;
7967         lastHint[0] = NULLCHAR;
7968         bookRequested = FALSE;
7969         /* Program may be pondering now */
7970         cps->maybeThinking = TRUE;
7971         if (cps->sendTime == 2) cps->sendTime = 1;
7972         if (cps->offeredDraw) cps->offeredDraw--;
7973
7974         /* [AS] Save move info*/
7975         pvInfoList[ forwardMostMove ].score = programStats.score;
7976         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7977         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7978
7979         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7980
7981         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7982         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7983             int count = 0;
7984
7985             while( count < adjudicateLossPlies ) {
7986                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7987
7988                 if( count & 1 ) {
7989                     score = -score; /* Flip score for winning side */
7990                 }
7991
7992                 if( score > adjudicateLossThreshold ) {
7993                     break;
7994                 }
7995
7996                 count++;
7997             }
7998
7999             if( count >= adjudicateLossPlies ) {
8000                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8001
8002                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8003                     "Xboard adjudication",
8004                     GE_XBOARD );
8005
8006                 return;
8007             }
8008         }
8009
8010         if(Adjudicate(cps)) {
8011             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8012             return; // [HGM] adjudicate: for all automatic game ends
8013         }
8014
8015 #if ZIPPY
8016         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8017             first.initDone) {
8018           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8019                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8020                 SendToICS("draw ");
8021                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8022           }
8023           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8024           ics_user_moved = 1;
8025           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8026                 char buf[3*MSG_SIZ];
8027
8028                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8029                         programStats.score / 100.,
8030                         programStats.depth,
8031                         programStats.time / 100.,
8032                         (unsigned int)programStats.nodes,
8033                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8034                         programStats.movelist);
8035                 SendToICS(buf);
8036 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8037           }
8038         }
8039 #endif
8040
8041         /* [AS] Clear stats for next move */
8042         ClearProgramStats();
8043         thinkOutput[0] = NULLCHAR;
8044         hiddenThinkOutputState = 0;
8045
8046         bookHit = NULL;
8047         if (gameMode == TwoMachinesPlay) {
8048             /* [HGM] relaying draw offers moved to after reception of move */
8049             /* and interpreting offer as claim if it brings draw condition */
8050             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8051                 SendToProgram("draw\n", cps->other);
8052             }
8053             if (cps->other->sendTime) {
8054                 SendTimeRemaining(cps->other,
8055                                   cps->other->twoMachinesColor[0] == 'w');
8056             }
8057             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8058             if (firstMove && !bookHit) {
8059                 firstMove = FALSE;
8060                 if (cps->other->useColors) {
8061                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8062                 }
8063                 SendToProgram("go\n", cps->other);
8064             }
8065             cps->other->maybeThinking = TRUE;
8066         }
8067
8068         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8069
8070         if (!pausing && appData.ringBellAfterMoves) {
8071             RingBell();
8072         }
8073
8074         /*
8075          * Reenable menu items that were disabled while
8076          * machine was thinking
8077          */
8078         if (gameMode != TwoMachinesPlay)
8079             SetUserThinkingEnables();
8080
8081         // [HGM] book: after book hit opponent has received move and is now in force mode
8082         // force the book reply into it, and then fake that it outputted this move by jumping
8083         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8084         if(bookHit) {
8085                 static char bookMove[MSG_SIZ]; // a bit generous?
8086
8087                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8088                 strcat(bookMove, bookHit);
8089                 message = bookMove;
8090                 cps = cps->other;
8091                 programStats.nodes = programStats.depth = programStats.time =
8092                 programStats.score = programStats.got_only_move = 0;
8093                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8094
8095                 if(cps->lastPing != cps->lastPong) {
8096                     savedMessage = message; // args for deferred call
8097                     savedState = cps;
8098                     ScheduleDelayedEvent(DeferredBookMove, 10);
8099                     return;
8100                 }
8101                 goto FakeBookMove;
8102         }
8103
8104         return;
8105     }
8106
8107     /* Set special modes for chess engines.  Later something general
8108      *  could be added here; for now there is just one kludge feature,
8109      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8110      *  when "xboard" is given as an interactive command.
8111      */
8112     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8113         cps->useSigint = FALSE;
8114         cps->useSigterm = FALSE;
8115     }
8116     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8117       ParseFeatures(message+8, cps);
8118       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8119     }
8120
8121     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8122                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8123       int dummy, s=6; char buf[MSG_SIZ];
8124       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8125       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8126       if(startedFromSetupPosition) return;
8127       ParseFEN(boards[0], &dummy, message+s);
8128       DrawPosition(TRUE, boards[0]);
8129       startedFromSetupPosition = TRUE;
8130       return;
8131     }
8132     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8133      * want this, I was asked to put it in, and obliged.
8134      */
8135     if (!strncmp(message, "setboard ", 9)) {
8136         Board initial_position;
8137
8138         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8139
8140         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8141             DisplayError(_("Bad FEN received from engine"), 0);
8142             return ;
8143         } else {
8144            Reset(TRUE, FALSE);
8145            CopyBoard(boards[0], initial_position);
8146            initialRulePlies = FENrulePlies;
8147            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8148            else gameMode = MachinePlaysBlack;
8149            DrawPosition(FALSE, boards[currentMove]);
8150         }
8151         return;
8152     }
8153
8154     /*
8155      * Look for communication commands
8156      */
8157     if (!strncmp(message, "telluser ", 9)) {
8158         if(message[9] == '\\' && message[10] == '\\')
8159             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8160         PlayTellSound();
8161         DisplayNote(message + 9);
8162         return;
8163     }
8164     if (!strncmp(message, "tellusererror ", 14)) {
8165         cps->userError = 1;
8166         if(message[14] == '\\' && message[15] == '\\')
8167             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8168         PlayTellSound();
8169         DisplayError(message + 14, 0);
8170         return;
8171     }
8172     if (!strncmp(message, "tellopponent ", 13)) {
8173       if (appData.icsActive) {
8174         if (loggedOn) {
8175           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8176           SendToICS(buf1);
8177         }
8178       } else {
8179         DisplayNote(message + 13);
8180       }
8181       return;
8182     }
8183     if (!strncmp(message, "tellothers ", 11)) {
8184       if (appData.icsActive) {
8185         if (loggedOn) {
8186           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8187           SendToICS(buf1);
8188         }
8189       }
8190       return;
8191     }
8192     if (!strncmp(message, "tellall ", 8)) {
8193       if (appData.icsActive) {
8194         if (loggedOn) {
8195           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8196           SendToICS(buf1);
8197         }
8198       } else {
8199         DisplayNote(message + 8);
8200       }
8201       return;
8202     }
8203     if (strncmp(message, "warning", 7) == 0) {
8204         /* Undocumented feature, use tellusererror in new code */
8205         DisplayError(message, 0);
8206         return;
8207     }
8208     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8209         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8210         strcat(realname, " query");
8211         AskQuestion(realname, buf2, buf1, cps->pr);
8212         return;
8213     }
8214     /* Commands from the engine directly to ICS.  We don't allow these to be
8215      *  sent until we are logged on. Crafty kibitzes have been known to
8216      *  interfere with the login process.
8217      */
8218     if (loggedOn) {
8219         if (!strncmp(message, "tellics ", 8)) {
8220             SendToICS(message + 8);
8221             SendToICS("\n");
8222             return;
8223         }
8224         if (!strncmp(message, "tellicsnoalias ", 15)) {
8225             SendToICS(ics_prefix);
8226             SendToICS(message + 15);
8227             SendToICS("\n");
8228             return;
8229         }
8230         /* The following are for backward compatibility only */
8231         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8232             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8233             SendToICS(ics_prefix);
8234             SendToICS(message);
8235             SendToICS("\n");
8236             return;
8237         }
8238     }
8239     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8240         return;
8241     }
8242     /*
8243      * If the move is illegal, cancel it and redraw the board.
8244      * Also deal with other error cases.  Matching is rather loose
8245      * here to accommodate engines written before the spec.
8246      */
8247     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8248         strncmp(message, "Error", 5) == 0) {
8249         if (StrStr(message, "name") ||
8250             StrStr(message, "rating") || StrStr(message, "?") ||
8251             StrStr(message, "result") || StrStr(message, "board") ||
8252             StrStr(message, "bk") || StrStr(message, "computer") ||
8253             StrStr(message, "variant") || StrStr(message, "hint") ||
8254             StrStr(message, "random") || StrStr(message, "depth") ||
8255             StrStr(message, "accepted")) {
8256             return;
8257         }
8258         if (StrStr(message, "protover")) {
8259           /* Program is responding to input, so it's apparently done
8260              initializing, and this error message indicates it is
8261              protocol version 1.  So we don't need to wait any longer
8262              for it to initialize and send feature commands. */
8263           FeatureDone(cps, 1);
8264           cps->protocolVersion = 1;
8265           return;
8266         }
8267         cps->maybeThinking = FALSE;
8268
8269         if (StrStr(message, "draw")) {
8270             /* Program doesn't have "draw" command */
8271             cps->sendDrawOffers = 0;
8272             return;
8273         }
8274         if (cps->sendTime != 1 &&
8275             (StrStr(message, "time") || StrStr(message, "otim"))) {
8276           /* Program apparently doesn't have "time" or "otim" command */
8277           cps->sendTime = 0;
8278           return;
8279         }
8280         if (StrStr(message, "analyze")) {
8281             cps->analysisSupport = FALSE;
8282             cps->analyzing = FALSE;
8283 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8284             EditGameEvent(); // [HGM] try to preserve loaded game
8285             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8286             DisplayError(buf2, 0);
8287             return;
8288         }
8289         if (StrStr(message, "(no matching move)st")) {
8290           /* Special kludge for GNU Chess 4 only */
8291           cps->stKludge = TRUE;
8292           SendTimeControl(cps, movesPerSession, timeControl,
8293                           timeIncrement, appData.searchDepth,
8294                           searchTime);
8295           return;
8296         }
8297         if (StrStr(message, "(no matching move)sd")) {
8298           /* Special kludge for GNU Chess 4 only */
8299           cps->sdKludge = TRUE;
8300           SendTimeControl(cps, movesPerSession, timeControl,
8301                           timeIncrement, appData.searchDepth,
8302                           searchTime);
8303           return;
8304         }
8305         if (!StrStr(message, "llegal")) {
8306             return;
8307         }
8308         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8309             gameMode == IcsIdle) return;
8310         if (forwardMostMove <= backwardMostMove) return;
8311         if (pausing) PauseEvent();
8312       if(appData.forceIllegal) {
8313             // [HGM] illegal: machine refused move; force position after move into it
8314           SendToProgram("force\n", cps);
8315           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8316                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8317                 // when black is to move, while there might be nothing on a2 or black
8318                 // might already have the move. So send the board as if white has the move.
8319                 // But first we must change the stm of the engine, as it refused the last move
8320                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8321                 if(WhiteOnMove(forwardMostMove)) {
8322                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8323                     SendBoard(cps, forwardMostMove); // kludgeless board
8324                 } else {
8325                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8326                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8327                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8328                 }
8329           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8330             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8331                  gameMode == TwoMachinesPlay)
8332               SendToProgram("go\n", cps);
8333             return;
8334       } else
8335         if (gameMode == PlayFromGameFile) {
8336             /* Stop reading this game file */
8337             gameMode = EditGame;
8338             ModeHighlight();
8339         }
8340         /* [HGM] illegal-move claim should forfeit game when Xboard */
8341         /* only passes fully legal moves                            */
8342         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8343             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8344                                 "False illegal-move claim", GE_XBOARD );
8345             return; // do not take back move we tested as valid
8346         }
8347         currentMove = forwardMostMove-1;
8348         DisplayMove(currentMove-1); /* before DisplayMoveError */
8349         SwitchClocks(forwardMostMove-1); // [HGM] race
8350         DisplayBothClocks();
8351         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8352                 parseList[currentMove], _(cps->which));
8353         DisplayMoveError(buf1);
8354         DrawPosition(FALSE, boards[currentMove]);
8355
8356         SetUserThinkingEnables();
8357         return;
8358     }
8359     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8360         /* Program has a broken "time" command that
8361            outputs a string not ending in newline.
8362            Don't use it. */
8363         cps->sendTime = 0;
8364     }
8365
8366     /*
8367      * If chess program startup fails, exit with an error message.
8368      * Attempts to recover here are futile.
8369      */
8370     if ((StrStr(message, "unknown host") != NULL)
8371         || (StrStr(message, "No remote directory") != NULL)
8372         || (StrStr(message, "not found") != NULL)
8373         || (StrStr(message, "No such file") != NULL)
8374         || (StrStr(message, "can't alloc") != NULL)
8375         || (StrStr(message, "Permission denied") != NULL)) {
8376
8377         cps->maybeThinking = FALSE;
8378         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8379                 _(cps->which), cps->program, cps->host, message);
8380         RemoveInputSource(cps->isr);
8381         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8382             if(cps == &first) appData.noChessProgram = TRUE;
8383             DisplayError(buf1, 0);
8384         }
8385         return;
8386     }
8387
8388     /*
8389      * Look for hint output
8390      */
8391     if (sscanf(message, "Hint: %s", buf1) == 1) {
8392         if (cps == &first && hintRequested) {
8393             hintRequested = FALSE;
8394             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8395                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8396                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8397                                     PosFlags(forwardMostMove),
8398                                     fromY, fromX, toY, toX, promoChar, buf1);
8399                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8400                 DisplayInformation(buf2);
8401             } else {
8402                 /* Hint move could not be parsed!? */
8403               snprintf(buf2, sizeof(buf2),
8404                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8405                         buf1, _(cps->which));
8406                 DisplayError(buf2, 0);
8407             }
8408         } else {
8409           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8410         }
8411         return;
8412     }
8413
8414     /*
8415      * Ignore other messages if game is not in progress
8416      */
8417     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8418         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8419
8420     /*
8421      * look for win, lose, draw, or draw offer
8422      */
8423     if (strncmp(message, "1-0", 3) == 0) {
8424         char *p, *q, *r = "";
8425         p = strchr(message, '{');
8426         if (p) {
8427             q = strchr(p, '}');
8428             if (q) {
8429                 *q = NULLCHAR;
8430                 r = p + 1;
8431             }
8432         }
8433         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8434         return;
8435     } else if (strncmp(message, "0-1", 3) == 0) {
8436         char *p, *q, *r = "";
8437         p = strchr(message, '{');
8438         if (p) {
8439             q = strchr(p, '}');
8440             if (q) {
8441                 *q = NULLCHAR;
8442                 r = p + 1;
8443             }
8444         }
8445         /* Kludge for Arasan 4.1 bug */
8446         if (strcmp(r, "Black resigns") == 0) {
8447             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8448             return;
8449         }
8450         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8451         return;
8452     } else if (strncmp(message, "1/2", 3) == 0) {
8453         char *p, *q, *r = "";
8454         p = strchr(message, '{');
8455         if (p) {
8456             q = strchr(p, '}');
8457             if (q) {
8458                 *q = NULLCHAR;
8459                 r = p + 1;
8460             }
8461         }
8462
8463         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8464         return;
8465
8466     } else if (strncmp(message, "White resign", 12) == 0) {
8467         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8468         return;
8469     } else if (strncmp(message, "Black resign", 12) == 0) {
8470         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8471         return;
8472     } else if (strncmp(message, "White matches", 13) == 0 ||
8473                strncmp(message, "Black matches", 13) == 0   ) {
8474         /* [HGM] ignore GNUShogi noises */
8475         return;
8476     } else if (strncmp(message, "White", 5) == 0 &&
8477                message[5] != '(' &&
8478                StrStr(message, "Black") == NULL) {
8479         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8480         return;
8481     } else if (strncmp(message, "Black", 5) == 0 &&
8482                message[5] != '(') {
8483         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8484         return;
8485     } else if (strcmp(message, "resign") == 0 ||
8486                strcmp(message, "computer resigns") == 0) {
8487         switch (gameMode) {
8488           case MachinePlaysBlack:
8489           case IcsPlayingBlack:
8490             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8491             break;
8492           case MachinePlaysWhite:
8493           case IcsPlayingWhite:
8494             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8495             break;
8496           case TwoMachinesPlay:
8497             if (cps->twoMachinesColor[0] == 'w')
8498               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8499             else
8500               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8501             break;
8502           default:
8503             /* can't happen */
8504             break;
8505         }
8506         return;
8507     } else if (strncmp(message, "opponent mates", 14) == 0) {
8508         switch (gameMode) {
8509           case MachinePlaysBlack:
8510           case IcsPlayingBlack:
8511             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8512             break;
8513           case MachinePlaysWhite:
8514           case IcsPlayingWhite:
8515             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8516             break;
8517           case TwoMachinesPlay:
8518             if (cps->twoMachinesColor[0] == 'w')
8519               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8520             else
8521               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8522             break;
8523           default:
8524             /* can't happen */
8525             break;
8526         }
8527         return;
8528     } else if (strncmp(message, "computer mates", 14) == 0) {
8529         switch (gameMode) {
8530           case MachinePlaysBlack:
8531           case IcsPlayingBlack:
8532             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8533             break;
8534           case MachinePlaysWhite:
8535           case IcsPlayingWhite:
8536             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8537             break;
8538           case TwoMachinesPlay:
8539             if (cps->twoMachinesColor[0] == 'w')
8540               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8541             else
8542               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8543             break;
8544           default:
8545             /* can't happen */
8546             break;
8547         }
8548         return;
8549     } else if (strncmp(message, "checkmate", 9) == 0) {
8550         if (WhiteOnMove(forwardMostMove)) {
8551             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8552         } else {
8553             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8554         }
8555         return;
8556     } else if (strstr(message, "Draw") != NULL ||
8557                strstr(message, "game is a draw") != NULL) {
8558         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8559         return;
8560     } else if (strstr(message, "offer") != NULL &&
8561                strstr(message, "draw") != NULL) {
8562 #if ZIPPY
8563         if (appData.zippyPlay && first.initDone) {
8564             /* Relay offer to ICS */
8565             SendToICS(ics_prefix);
8566             SendToICS("draw\n");
8567         }
8568 #endif
8569         cps->offeredDraw = 2; /* valid until this engine moves twice */
8570         if (gameMode == TwoMachinesPlay) {
8571             if (cps->other->offeredDraw) {
8572                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8573             /* [HGM] in two-machine mode we delay relaying draw offer      */
8574             /* until after we also have move, to see if it is really claim */
8575             }
8576         } else if (gameMode == MachinePlaysWhite ||
8577                    gameMode == MachinePlaysBlack) {
8578           if (userOfferedDraw) {
8579             DisplayInformation(_("Machine accepts your draw offer"));
8580             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8581           } else {
8582             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8583           }
8584         }
8585     }
8586
8587
8588     /*
8589      * Look for thinking output
8590      */
8591     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8592           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8593                                 ) {
8594         int plylev, mvleft, mvtot, curscore, time;
8595         char mvname[MOVE_LEN];
8596         u64 nodes; // [DM]
8597         char plyext;
8598         int ignore = FALSE;
8599         int prefixHint = FALSE;
8600         mvname[0] = NULLCHAR;
8601
8602         switch (gameMode) {
8603           case MachinePlaysBlack:
8604           case IcsPlayingBlack:
8605             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8606             break;
8607           case MachinePlaysWhite:
8608           case IcsPlayingWhite:
8609             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8610             break;
8611           case AnalyzeMode:
8612           case AnalyzeFile:
8613             break;
8614           case IcsObserving: /* [DM] icsEngineAnalyze */
8615             if (!appData.icsEngineAnalyze) ignore = TRUE;
8616             break;
8617           case TwoMachinesPlay:
8618             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8619                 ignore = TRUE;
8620             }
8621             break;
8622           default:
8623             ignore = TRUE;
8624             break;
8625         }
8626
8627         if (!ignore) {
8628             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8629             buf1[0] = NULLCHAR;
8630             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8631                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8632
8633                 if (plyext != ' ' && plyext != '\t') {
8634                     time *= 100;
8635                 }
8636
8637                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8638                 if( cps->scoreIsAbsolute &&
8639                     ( gameMode == MachinePlaysBlack ||
8640                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8641                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8642                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8643                      !WhiteOnMove(currentMove)
8644                     ) )
8645                 {
8646                     curscore = -curscore;
8647                 }
8648
8649                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8650
8651                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8652                         char buf[MSG_SIZ];
8653                         FILE *f;
8654                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8655                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8656                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8657                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8658                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8659                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8660                                 fclose(f);
8661                         } else DisplayError(_("failed writing PV"), 0);
8662                 }
8663
8664                 tempStats.depth = plylev;
8665                 tempStats.nodes = nodes;
8666                 tempStats.time = time;
8667                 tempStats.score = curscore;
8668                 tempStats.got_only_move = 0;
8669
8670                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8671                         int ticklen;
8672
8673                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8674                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8675                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8676                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8677                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8678                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8679                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8680                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8681                 }
8682
8683                 /* Buffer overflow protection */
8684                 if (pv[0] != NULLCHAR) {
8685                     if (strlen(pv) >= sizeof(tempStats.movelist)
8686                         && appData.debugMode) {
8687                         fprintf(debugFP,
8688                                 "PV is too long; using the first %u bytes.\n",
8689                                 (unsigned) sizeof(tempStats.movelist) - 1);
8690                     }
8691
8692                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8693                 } else {
8694                     sprintf(tempStats.movelist, " no PV\n");
8695                 }
8696
8697                 if (tempStats.seen_stat) {
8698                     tempStats.ok_to_send = 1;
8699                 }
8700
8701                 if (strchr(tempStats.movelist, '(') != NULL) {
8702                     tempStats.line_is_book = 1;
8703                     tempStats.nr_moves = 0;
8704                     tempStats.moves_left = 0;
8705                 } else {
8706                     tempStats.line_is_book = 0;
8707                 }
8708
8709                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8710                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8711
8712                 SendProgramStatsToFrontend( cps, &tempStats );
8713
8714                 /*
8715                     [AS] Protect the thinkOutput buffer from overflow... this
8716                     is only useful if buf1 hasn't overflowed first!
8717                 */
8718                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8719                          plylev,
8720                          (gameMode == TwoMachinesPlay ?
8721                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8722                          ((double) curscore) / 100.0,
8723                          prefixHint ? lastHint : "",
8724                          prefixHint ? " " : "" );
8725
8726                 if( buf1[0] != NULLCHAR ) {
8727                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8728
8729                     if( strlen(pv) > max_len ) {
8730                         if( appData.debugMode) {
8731                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8732                         }
8733                         pv[max_len+1] = '\0';
8734                     }
8735
8736                     strcat( thinkOutput, pv);
8737                 }
8738
8739                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8740                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8741                     DisplayMove(currentMove - 1);
8742                 }
8743                 return;
8744
8745             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8746                 /* crafty (9.25+) says "(only move) <move>"
8747                  * if there is only 1 legal move
8748                  */
8749                 sscanf(p, "(only move) %s", buf1);
8750                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8751                 sprintf(programStats.movelist, "%s (only move)", buf1);
8752                 programStats.depth = 1;
8753                 programStats.nr_moves = 1;
8754                 programStats.moves_left = 1;
8755                 programStats.nodes = 1;
8756                 programStats.time = 1;
8757                 programStats.got_only_move = 1;
8758
8759                 /* Not really, but we also use this member to
8760                    mean "line isn't going to change" (Crafty
8761                    isn't searching, so stats won't change) */
8762                 programStats.line_is_book = 1;
8763
8764                 SendProgramStatsToFrontend( cps, &programStats );
8765
8766                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8767                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8768                     DisplayMove(currentMove - 1);
8769                 }
8770                 return;
8771             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8772                               &time, &nodes, &plylev, &mvleft,
8773                               &mvtot, mvname) >= 5) {
8774                 /* The stat01: line is from Crafty (9.29+) in response
8775                    to the "." command */
8776                 programStats.seen_stat = 1;
8777                 cps->maybeThinking = TRUE;
8778
8779                 if (programStats.got_only_move || !appData.periodicUpdates)
8780                   return;
8781
8782                 programStats.depth = plylev;
8783                 programStats.time = time;
8784                 programStats.nodes = nodes;
8785                 programStats.moves_left = mvleft;
8786                 programStats.nr_moves = mvtot;
8787                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8788                 programStats.ok_to_send = 1;
8789                 programStats.movelist[0] = '\0';
8790
8791                 SendProgramStatsToFrontend( cps, &programStats );
8792
8793                 return;
8794
8795             } else if (strncmp(message,"++",2) == 0) {
8796                 /* Crafty 9.29+ outputs this */
8797                 programStats.got_fail = 2;
8798                 return;
8799
8800             } else if (strncmp(message,"--",2) == 0) {
8801                 /* Crafty 9.29+ outputs this */
8802                 programStats.got_fail = 1;
8803                 return;
8804
8805             } else if (thinkOutput[0] != NULLCHAR &&
8806                        strncmp(message, "    ", 4) == 0) {
8807                 unsigned message_len;
8808
8809                 p = message;
8810                 while (*p && *p == ' ') p++;
8811
8812                 message_len = strlen( p );
8813
8814                 /* [AS] Avoid buffer overflow */
8815                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8816                     strcat(thinkOutput, " ");
8817                     strcat(thinkOutput, p);
8818                 }
8819
8820                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8821                     strcat(programStats.movelist, " ");
8822                     strcat(programStats.movelist, p);
8823                 }
8824
8825                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8826                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8827                     DisplayMove(currentMove - 1);
8828                 }
8829                 return;
8830             }
8831         }
8832         else {
8833             buf1[0] = NULLCHAR;
8834
8835             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8836                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8837             {
8838                 ChessProgramStats cpstats;
8839
8840                 if (plyext != ' ' && plyext != '\t') {
8841                     time *= 100;
8842                 }
8843
8844                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8845                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8846                     curscore = -curscore;
8847                 }
8848
8849                 cpstats.depth = plylev;
8850                 cpstats.nodes = nodes;
8851                 cpstats.time = time;
8852                 cpstats.score = curscore;
8853                 cpstats.got_only_move = 0;
8854                 cpstats.movelist[0] = '\0';
8855
8856                 if (buf1[0] != NULLCHAR) {
8857                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8858                 }
8859
8860                 cpstats.ok_to_send = 0;
8861                 cpstats.line_is_book = 0;
8862                 cpstats.nr_moves = 0;
8863                 cpstats.moves_left = 0;
8864
8865                 SendProgramStatsToFrontend( cps, &cpstats );
8866             }
8867         }
8868     }
8869 }
8870
8871
8872 /* Parse a game score from the character string "game", and
8873    record it as the history of the current game.  The game
8874    score is NOT assumed to start from the standard position.
8875    The display is not updated in any way.
8876    */
8877 void
8878 ParseGameHistory (char *game)
8879 {
8880     ChessMove moveType;
8881     int fromX, fromY, toX, toY, boardIndex;
8882     char promoChar;
8883     char *p, *q;
8884     char buf[MSG_SIZ];
8885
8886     if (appData.debugMode)
8887       fprintf(debugFP, "Parsing game history: %s\n", game);
8888
8889     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8890     gameInfo.site = StrSave(appData.icsHost);
8891     gameInfo.date = PGNDate();
8892     gameInfo.round = StrSave("-");
8893
8894     /* Parse out names of players */
8895     while (*game == ' ') game++;
8896     p = buf;
8897     while (*game != ' ') *p++ = *game++;
8898     *p = NULLCHAR;
8899     gameInfo.white = StrSave(buf);
8900     while (*game == ' ') game++;
8901     p = buf;
8902     while (*game != ' ' && *game != '\n') *p++ = *game++;
8903     *p = NULLCHAR;
8904     gameInfo.black = StrSave(buf);
8905
8906     /* Parse moves */
8907     boardIndex = blackPlaysFirst ? 1 : 0;
8908     yynewstr(game);
8909     for (;;) {
8910         yyboardindex = boardIndex;
8911         moveType = (ChessMove) Myylex();
8912         switch (moveType) {
8913           case IllegalMove:             /* maybe suicide chess, etc. */
8914   if (appData.debugMode) {
8915     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8916     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8917     setbuf(debugFP, NULL);
8918   }
8919           case WhitePromotion:
8920           case BlackPromotion:
8921           case WhiteNonPromotion:
8922           case BlackNonPromotion:
8923           case NormalMove:
8924           case WhiteCapturesEnPassant:
8925           case BlackCapturesEnPassant:
8926           case WhiteKingSideCastle:
8927           case WhiteQueenSideCastle:
8928           case BlackKingSideCastle:
8929           case BlackQueenSideCastle:
8930           case WhiteKingSideCastleWild:
8931           case WhiteQueenSideCastleWild:
8932           case BlackKingSideCastleWild:
8933           case BlackQueenSideCastleWild:
8934           /* PUSH Fabien */
8935           case WhiteHSideCastleFR:
8936           case WhiteASideCastleFR:
8937           case BlackHSideCastleFR:
8938           case BlackASideCastleFR:
8939           /* POP Fabien */
8940             fromX = currentMoveString[0] - AAA;
8941             fromY = currentMoveString[1] - ONE;
8942             toX = currentMoveString[2] - AAA;
8943             toY = currentMoveString[3] - ONE;
8944             promoChar = currentMoveString[4];
8945             break;
8946           case WhiteDrop:
8947           case BlackDrop:
8948             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8949             fromX = moveType == WhiteDrop ?
8950               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8951             (int) CharToPiece(ToLower(currentMoveString[0]));
8952             fromY = DROP_RANK;
8953             toX = currentMoveString[2] - AAA;
8954             toY = currentMoveString[3] - ONE;
8955             promoChar = NULLCHAR;
8956             break;
8957           case AmbiguousMove:
8958             /* bug? */
8959             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8960   if (appData.debugMode) {
8961     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8962     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8963     setbuf(debugFP, NULL);
8964   }
8965             DisplayError(buf, 0);
8966             return;
8967           case ImpossibleMove:
8968             /* bug? */
8969             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8970   if (appData.debugMode) {
8971     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8972     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8973     setbuf(debugFP, NULL);
8974   }
8975             DisplayError(buf, 0);
8976             return;
8977           case EndOfFile:
8978             if (boardIndex < backwardMostMove) {
8979                 /* Oops, gap.  How did that happen? */
8980                 DisplayError(_("Gap in move list"), 0);
8981                 return;
8982             }
8983             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8984             if (boardIndex > forwardMostMove) {
8985                 forwardMostMove = boardIndex;
8986             }
8987             return;
8988           case ElapsedTime:
8989             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8990                 strcat(parseList[boardIndex-1], " ");
8991                 strcat(parseList[boardIndex-1], yy_text);
8992             }
8993             continue;
8994           case Comment:
8995           case PGNTag:
8996           case NAG:
8997           default:
8998             /* ignore */
8999             continue;
9000           case WhiteWins:
9001           case BlackWins:
9002           case GameIsDrawn:
9003           case GameUnfinished:
9004             if (gameMode == IcsExamining) {
9005                 if (boardIndex < backwardMostMove) {
9006                     /* Oops, gap.  How did that happen? */
9007                     return;
9008                 }
9009                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9010                 return;
9011             }
9012             gameInfo.result = moveType;
9013             p = strchr(yy_text, '{');
9014             if (p == NULL) p = strchr(yy_text, '(');
9015             if (p == NULL) {
9016                 p = yy_text;
9017                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9018             } else {
9019                 q = strchr(p, *p == '{' ? '}' : ')');
9020                 if (q != NULL) *q = NULLCHAR;
9021                 p++;
9022             }
9023             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9024             gameInfo.resultDetails = StrSave(p);
9025             continue;
9026         }
9027         if (boardIndex >= forwardMostMove &&
9028             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9029             backwardMostMove = blackPlaysFirst ? 1 : 0;
9030             return;
9031         }
9032         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9033                                  fromY, fromX, toY, toX, promoChar,
9034                                  parseList[boardIndex]);
9035         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9036         /* currentMoveString is set as a side-effect of yylex */
9037         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9038         strcat(moveList[boardIndex], "\n");
9039         boardIndex++;
9040         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9041         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9042           case MT_NONE:
9043           case MT_STALEMATE:
9044           default:
9045             break;
9046           case MT_CHECK:
9047             if(gameInfo.variant != VariantShogi)
9048                 strcat(parseList[boardIndex - 1], "+");
9049             break;
9050           case MT_CHECKMATE:
9051           case MT_STAINMATE:
9052             strcat(parseList[boardIndex - 1], "#");
9053             break;
9054         }
9055     }
9056 }
9057
9058
9059 /* Apply a move to the given board  */
9060 void
9061 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9062 {
9063   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9064   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9065
9066     /* [HGM] compute & store e.p. status and castling rights for new position */
9067     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9068
9069       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9070       oldEP = (signed char)board[EP_STATUS];
9071       board[EP_STATUS] = EP_NONE;
9072
9073   if (fromY == DROP_RANK) {
9074         /* must be first */
9075         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9076             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9077             return;
9078         }
9079         piece = board[toY][toX] = (ChessSquare) fromX;
9080   } else {
9081       int i;
9082
9083       if( board[toY][toX] != EmptySquare )
9084            board[EP_STATUS] = EP_CAPTURE;
9085
9086       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9087            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9088                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9089       } else
9090       if( board[fromY][fromX] == WhitePawn ) {
9091            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9092                board[EP_STATUS] = EP_PAWN_MOVE;
9093            if( toY-fromY==2) {
9094                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9095                         gameInfo.variant != VariantBerolina || toX < fromX)
9096                       board[EP_STATUS] = toX | berolina;
9097                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9098                         gameInfo.variant != VariantBerolina || toX > fromX)
9099                       board[EP_STATUS] = toX;
9100            }
9101       } else
9102       if( board[fromY][fromX] == BlackPawn ) {
9103            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9104                board[EP_STATUS] = EP_PAWN_MOVE;
9105            if( toY-fromY== -2) {
9106                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9107                         gameInfo.variant != VariantBerolina || toX < fromX)
9108                       board[EP_STATUS] = toX | berolina;
9109                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9110                         gameInfo.variant != VariantBerolina || toX > fromX)
9111                       board[EP_STATUS] = toX;
9112            }
9113        }
9114
9115        for(i=0; i<nrCastlingRights; i++) {
9116            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9117               board[CASTLING][i] == toX   && castlingRank[i] == toY
9118              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9119        }
9120
9121      if (fromX == toX && fromY == toY) return;
9122
9123      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9124      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9125      if(gameInfo.variant == VariantKnightmate)
9126          king += (int) WhiteUnicorn - (int) WhiteKing;
9127
9128     /* Code added by Tord: */
9129     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9130     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9131         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9132       board[fromY][fromX] = EmptySquare;
9133       board[toY][toX] = EmptySquare;
9134       if((toX > fromX) != (piece == WhiteRook)) {
9135         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9136       } else {
9137         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9138       }
9139     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9140                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9141       board[fromY][fromX] = EmptySquare;
9142       board[toY][toX] = EmptySquare;
9143       if((toX > fromX) != (piece == BlackRook)) {
9144         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9145       } else {
9146         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9147       }
9148     /* End of code added by Tord */
9149
9150     } else if (board[fromY][fromX] == king
9151         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9152         && toY == fromY && toX > fromX+1) {
9153         board[fromY][fromX] = EmptySquare;
9154         board[toY][toX] = king;
9155         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9156         board[fromY][BOARD_RGHT-1] = EmptySquare;
9157     } else if (board[fromY][fromX] == king
9158         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9159                && toY == fromY && toX < fromX-1) {
9160         board[fromY][fromX] = EmptySquare;
9161         board[toY][toX] = king;
9162         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9163         board[fromY][BOARD_LEFT] = EmptySquare;
9164     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9165                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9166                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9167                ) {
9168         /* white pawn promotion */
9169         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9170         if(gameInfo.variant==VariantBughouse ||
9171            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9172             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9173         board[fromY][fromX] = EmptySquare;
9174     } else if ((fromY >= BOARD_HEIGHT>>1)
9175                && (toX != fromX)
9176                && gameInfo.variant != VariantXiangqi
9177                && gameInfo.variant != VariantBerolina
9178                && (board[fromY][fromX] == WhitePawn)
9179                && (board[toY][toX] == EmptySquare)) {
9180         board[fromY][fromX] = EmptySquare;
9181         board[toY][toX] = WhitePawn;
9182         captured = board[toY - 1][toX];
9183         board[toY - 1][toX] = EmptySquare;
9184     } else if ((fromY == BOARD_HEIGHT-4)
9185                && (toX == fromX)
9186                && gameInfo.variant == VariantBerolina
9187                && (board[fromY][fromX] == WhitePawn)
9188                && (board[toY][toX] == EmptySquare)) {
9189         board[fromY][fromX] = EmptySquare;
9190         board[toY][toX] = WhitePawn;
9191         if(oldEP & EP_BEROLIN_A) {
9192                 captured = board[fromY][fromX-1];
9193                 board[fromY][fromX-1] = EmptySquare;
9194         }else{  captured = board[fromY][fromX+1];
9195                 board[fromY][fromX+1] = EmptySquare;
9196         }
9197     } else if (board[fromY][fromX] == king
9198         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9199                && toY == fromY && toX > fromX+1) {
9200         board[fromY][fromX] = EmptySquare;
9201         board[toY][toX] = king;
9202         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9203         board[fromY][BOARD_RGHT-1] = EmptySquare;
9204     } else if (board[fromY][fromX] == king
9205         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9206                && toY == fromY && toX < fromX-1) {
9207         board[fromY][fromX] = EmptySquare;
9208         board[toY][toX] = king;
9209         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9210         board[fromY][BOARD_LEFT] = EmptySquare;
9211     } else if (fromY == 7 && fromX == 3
9212                && board[fromY][fromX] == BlackKing
9213                && toY == 7 && toX == 5) {
9214         board[fromY][fromX] = EmptySquare;
9215         board[toY][toX] = BlackKing;
9216         board[fromY][7] = EmptySquare;
9217         board[toY][4] = BlackRook;
9218     } else if (fromY == 7 && fromX == 3
9219                && board[fromY][fromX] == BlackKing
9220                && toY == 7 && toX == 1) {
9221         board[fromY][fromX] = EmptySquare;
9222         board[toY][toX] = BlackKing;
9223         board[fromY][0] = EmptySquare;
9224         board[toY][2] = BlackRook;
9225     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9226                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9227                && toY < promoRank && promoChar
9228                ) {
9229         /* black pawn promotion */
9230         board[toY][toX] = CharToPiece(ToLower(promoChar));
9231         if(gameInfo.variant==VariantBughouse ||
9232            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9233             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9234         board[fromY][fromX] = EmptySquare;
9235     } else if ((fromY < BOARD_HEIGHT>>1)
9236                && (toX != fromX)
9237                && gameInfo.variant != VariantXiangqi
9238                && gameInfo.variant != VariantBerolina
9239                && (board[fromY][fromX] == BlackPawn)
9240                && (board[toY][toX] == EmptySquare)) {
9241         board[fromY][fromX] = EmptySquare;
9242         board[toY][toX] = BlackPawn;
9243         captured = board[toY + 1][toX];
9244         board[toY + 1][toX] = EmptySquare;
9245     } else if ((fromY == 3)
9246                && (toX == fromX)
9247                && gameInfo.variant == VariantBerolina
9248                && (board[fromY][fromX] == BlackPawn)
9249                && (board[toY][toX] == EmptySquare)) {
9250         board[fromY][fromX] = EmptySquare;
9251         board[toY][toX] = BlackPawn;
9252         if(oldEP & EP_BEROLIN_A) {
9253                 captured = board[fromY][fromX-1];
9254                 board[fromY][fromX-1] = EmptySquare;
9255         }else{  captured = board[fromY][fromX+1];
9256                 board[fromY][fromX+1] = EmptySquare;
9257         }
9258     } else {
9259         board[toY][toX] = board[fromY][fromX];
9260         board[fromY][fromX] = EmptySquare;
9261     }
9262   }
9263
9264     if (gameInfo.holdingsWidth != 0) {
9265
9266       /* !!A lot more code needs to be written to support holdings  */
9267       /* [HGM] OK, so I have written it. Holdings are stored in the */
9268       /* penultimate board files, so they are automaticlly stored   */
9269       /* in the game history.                                       */
9270       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9271                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9272         /* Delete from holdings, by decreasing count */
9273         /* and erasing image if necessary            */
9274         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9275         if(p < (int) BlackPawn) { /* white drop */
9276              p -= (int)WhitePawn;
9277                  p = PieceToNumber((ChessSquare)p);
9278              if(p >= gameInfo.holdingsSize) p = 0;
9279              if(--board[p][BOARD_WIDTH-2] <= 0)
9280                   board[p][BOARD_WIDTH-1] = EmptySquare;
9281              if((int)board[p][BOARD_WIDTH-2] < 0)
9282                         board[p][BOARD_WIDTH-2] = 0;
9283         } else {                  /* black drop */
9284              p -= (int)BlackPawn;
9285                  p = PieceToNumber((ChessSquare)p);
9286              if(p >= gameInfo.holdingsSize) p = 0;
9287              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9288                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9289              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9290                         board[BOARD_HEIGHT-1-p][1] = 0;
9291         }
9292       }
9293       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9294           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9295         /* [HGM] holdings: Add to holdings, if holdings exist */
9296         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9297                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9298                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9299         }
9300         p = (int) captured;
9301         if (p >= (int) BlackPawn) {
9302           p -= (int)BlackPawn;
9303           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9304                   /* in Shogi restore piece to its original  first */
9305                   captured = (ChessSquare) (DEMOTED captured);
9306                   p = DEMOTED p;
9307           }
9308           p = PieceToNumber((ChessSquare)p);
9309           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9310           board[p][BOARD_WIDTH-2]++;
9311           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9312         } else {
9313           p -= (int)WhitePawn;
9314           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9315                   captured = (ChessSquare) (DEMOTED captured);
9316                   p = DEMOTED p;
9317           }
9318           p = PieceToNumber((ChessSquare)p);
9319           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9320           board[BOARD_HEIGHT-1-p][1]++;
9321           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9322         }
9323       }
9324     } else if (gameInfo.variant == VariantAtomic) {
9325       if (captured != EmptySquare) {
9326         int y, x;
9327         for (y = toY-1; y <= toY+1; y++) {
9328           for (x = toX-1; x <= toX+1; x++) {
9329             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9330                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9331               board[y][x] = EmptySquare;
9332             }
9333           }
9334         }
9335         board[toY][toX] = EmptySquare;
9336       }
9337     }
9338     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9339         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9340     } else
9341     if(promoChar == '+') {
9342         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9343         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9344     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9345         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9346     }
9347     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9348                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9349         // [HGM] superchess: take promotion piece out of holdings
9350         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9351         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9352             if(!--board[k][BOARD_WIDTH-2])
9353                 board[k][BOARD_WIDTH-1] = EmptySquare;
9354         } else {
9355             if(!--board[BOARD_HEIGHT-1-k][1])
9356                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9357         }
9358     }
9359
9360 }
9361
9362 /* Updates forwardMostMove */
9363 void
9364 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9365 {
9366 //    forwardMostMove++; // [HGM] bare: moved downstream
9367
9368     (void) CoordsToAlgebraic(boards[forwardMostMove],
9369                              PosFlags(forwardMostMove),
9370                              fromY, fromX, toY, toX, promoChar,
9371                              parseList[forwardMostMove]);
9372
9373     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9374         int timeLeft; static int lastLoadFlag=0; int king, piece;
9375         piece = boards[forwardMostMove][fromY][fromX];
9376         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9377         if(gameInfo.variant == VariantKnightmate)
9378             king += (int) WhiteUnicorn - (int) WhiteKing;
9379         if(forwardMostMove == 0) {
9380             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9381                 fprintf(serverMoves, "%s;", UserName());
9382             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9383                 fprintf(serverMoves, "%s;", second.tidy);
9384             fprintf(serverMoves, "%s;", first.tidy);
9385             if(gameMode == MachinePlaysWhite)
9386                 fprintf(serverMoves, "%s;", UserName());
9387             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9388                 fprintf(serverMoves, "%s;", second.tidy);
9389         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9390         lastLoadFlag = loadFlag;
9391         // print base move
9392         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9393         // print castling suffix
9394         if( toY == fromY && piece == king ) {
9395             if(toX-fromX > 1)
9396                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9397             if(fromX-toX >1)
9398                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9399         }
9400         // e.p. suffix
9401         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9402              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9403              boards[forwardMostMove][toY][toX] == EmptySquare
9404              && fromX != toX && fromY != toY)
9405                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9406         // promotion suffix
9407         if(promoChar != NULLCHAR)
9408                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9409         if(!loadFlag) {
9410                 char buf[MOVE_LEN*2], *p; int len;
9411             fprintf(serverMoves, "/%d/%d",
9412                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9413             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9414             else                      timeLeft = blackTimeRemaining/1000;
9415             fprintf(serverMoves, "/%d", timeLeft);
9416                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9417                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9418                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9419             fprintf(serverMoves, "/%s", buf);
9420         }
9421         fflush(serverMoves);
9422     }
9423
9424     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9425         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9426       return;
9427     }
9428     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9429     if (commentList[forwardMostMove+1] != NULL) {
9430         free(commentList[forwardMostMove+1]);
9431         commentList[forwardMostMove+1] = NULL;
9432     }
9433     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9434     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9435     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9436     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9437     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9438     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9439     adjustedClock = FALSE;
9440     gameInfo.result = GameUnfinished;
9441     if (gameInfo.resultDetails != NULL) {
9442         free(gameInfo.resultDetails);
9443         gameInfo.resultDetails = NULL;
9444     }
9445     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9446                               moveList[forwardMostMove - 1]);
9447     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9448       case MT_NONE:
9449       case MT_STALEMATE:
9450       default:
9451         break;
9452       case MT_CHECK:
9453         if(gameInfo.variant != VariantShogi)
9454             strcat(parseList[forwardMostMove - 1], "+");
9455         break;
9456       case MT_CHECKMATE:
9457       case MT_STAINMATE:
9458         strcat(parseList[forwardMostMove - 1], "#");
9459         break;
9460     }
9461     if (appData.debugMode) {
9462         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9463     }
9464
9465 }
9466
9467 /* Updates currentMove if not pausing */
9468 void
9469 ShowMove (int fromX, int fromY, int toX, int toY)
9470 {
9471     int instant = (gameMode == PlayFromGameFile) ?
9472         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9473     if(appData.noGUI) return;
9474     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9475         if (!instant) {
9476             if (forwardMostMove == currentMove + 1) {
9477                 AnimateMove(boards[forwardMostMove - 1],
9478                             fromX, fromY, toX, toY);
9479             }
9480             if (appData.highlightLastMove) {
9481                 SetHighlights(fromX, fromY, toX, toY);
9482             }
9483         }
9484         currentMove = forwardMostMove;
9485     }
9486
9487     if (instant) return;
9488
9489     DisplayMove(currentMove - 1);
9490     DrawPosition(FALSE, boards[currentMove]);
9491     DisplayBothClocks();
9492     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9493 }
9494
9495 void
9496 SendEgtPath (ChessProgramState *cps)
9497 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9498         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9499
9500         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9501
9502         while(*p) {
9503             char c, *q = name+1, *r, *s;
9504
9505             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9506             while(*p && *p != ',') *q++ = *p++;
9507             *q++ = ':'; *q = 0;
9508             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9509                 strcmp(name, ",nalimov:") == 0 ) {
9510                 // take nalimov path from the menu-changeable option first, if it is defined
9511               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9512                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9513             } else
9514             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9515                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9516                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9517                 s = r = StrStr(s, ":") + 1; // beginning of path info
9518                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9519                 c = *r; *r = 0;             // temporarily null-terminate path info
9520                     *--q = 0;               // strip of trailig ':' from name
9521                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9522                 *r = c;
9523                 SendToProgram(buf,cps);     // send egtbpath command for this format
9524             }
9525             if(*p == ',') p++; // read away comma to position for next format name
9526         }
9527 }
9528
9529 void
9530 InitChessProgram (ChessProgramState *cps, int setup)
9531 /* setup needed to setup FRC opening position */
9532 {
9533     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9534     if (appData.noChessProgram) return;
9535     hintRequested = FALSE;
9536     bookRequested = FALSE;
9537
9538     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9539     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9540     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9541     if(cps->memSize) { /* [HGM] memory */
9542       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9543         SendToProgram(buf, cps);
9544     }
9545     SendEgtPath(cps); /* [HGM] EGT */
9546     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9547       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9548         SendToProgram(buf, cps);
9549     }
9550
9551     SendToProgram(cps->initString, cps);
9552     if (gameInfo.variant != VariantNormal &&
9553         gameInfo.variant != VariantLoadable
9554         /* [HGM] also send variant if board size non-standard */
9555         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9556                                             ) {
9557       char *v = VariantName(gameInfo.variant);
9558       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9559         /* [HGM] in protocol 1 we have to assume all variants valid */
9560         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9561         DisplayFatalError(buf, 0, 1);
9562         return;
9563       }
9564
9565       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9566       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9567       if( gameInfo.variant == VariantXiangqi )
9568            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9569       if( gameInfo.variant == VariantShogi )
9570            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9571       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9572            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9573       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9574           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9575            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9576       if( gameInfo.variant == VariantCourier )
9577            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9578       if( gameInfo.variant == VariantSuper )
9579            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9580       if( gameInfo.variant == VariantGreat )
9581            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9582       if( gameInfo.variant == VariantSChess )
9583            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9584       if( gameInfo.variant == VariantGrand )
9585            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9586
9587       if(overruled) {
9588         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9589                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9590            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9591            if(StrStr(cps->variants, b) == NULL) {
9592                // specific sized variant not known, check if general sizing allowed
9593                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9594                    if(StrStr(cps->variants, "boardsize") == NULL) {
9595                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9596                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9597                        DisplayFatalError(buf, 0, 1);
9598                        return;
9599                    }
9600                    /* [HGM] here we really should compare with the maximum supported board size */
9601                }
9602            }
9603       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9604       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9605       SendToProgram(buf, cps);
9606     }
9607     currentlyInitializedVariant = gameInfo.variant;
9608
9609     /* [HGM] send opening position in FRC to first engine */
9610     if(setup) {
9611           SendToProgram("force\n", cps);
9612           SendBoard(cps, 0);
9613           /* engine is now in force mode! Set flag to wake it up after first move. */
9614           setboardSpoiledMachineBlack = 1;
9615     }
9616
9617     if (cps->sendICS) {
9618       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9619       SendToProgram(buf, cps);
9620     }
9621     cps->maybeThinking = FALSE;
9622     cps->offeredDraw = 0;
9623     if (!appData.icsActive) {
9624         SendTimeControl(cps, movesPerSession, timeControl,
9625                         timeIncrement, appData.searchDepth,
9626                         searchTime);
9627     }
9628     if (appData.showThinking
9629         // [HGM] thinking: four options require thinking output to be sent
9630         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9631                                 ) {
9632         SendToProgram("post\n", cps);
9633     }
9634     SendToProgram("hard\n", cps);
9635     if (!appData.ponderNextMove) {
9636         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9637            it without being sure what state we are in first.  "hard"
9638            is not a toggle, so that one is OK.
9639          */
9640         SendToProgram("easy\n", cps);
9641     }
9642     if (cps->usePing) {
9643       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9644       SendToProgram(buf, cps);
9645     }
9646     cps->initDone = TRUE;
9647     ClearEngineOutputPane(cps == &second);
9648 }
9649
9650
9651 void
9652 StartChessProgram (ChessProgramState *cps)
9653 {
9654     char buf[MSG_SIZ];
9655     int err;
9656
9657     if (appData.noChessProgram) return;
9658     cps->initDone = FALSE;
9659
9660     if (strcmp(cps->host, "localhost") == 0) {
9661         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9662     } else if (*appData.remoteShell == NULLCHAR) {
9663         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9664     } else {
9665         if (*appData.remoteUser == NULLCHAR) {
9666           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9667                     cps->program);
9668         } else {
9669           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9670                     cps->host, appData.remoteUser, cps->program);
9671         }
9672         err = StartChildProcess(buf, "", &cps->pr);
9673     }
9674
9675     if (err != 0) {
9676       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9677         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9678         if(cps != &first) return;
9679         appData.noChessProgram = TRUE;
9680         ThawUI();
9681         SetNCPMode();
9682 //      DisplayFatalError(buf, err, 1);
9683 //      cps->pr = NoProc;
9684 //      cps->isr = NULL;
9685         return;
9686     }
9687
9688     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9689     if (cps->protocolVersion > 1) {
9690       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9691       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9692       cps->comboCnt = 0;  //                and values of combo boxes
9693       SendToProgram(buf, cps);
9694     } else {
9695       SendToProgram("xboard\n", cps);
9696     }
9697 }
9698
9699 void
9700 TwoMachinesEventIfReady P((void))
9701 {
9702   static int curMess = 0;
9703   if (first.lastPing != first.lastPong) {
9704     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9705     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9706     return;
9707   }
9708   if (second.lastPing != second.lastPong) {
9709     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9710     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9711     return;
9712   }
9713   DisplayMessage("", ""); curMess = 0;
9714   ThawUI();
9715   TwoMachinesEvent();
9716 }
9717
9718 char *
9719 MakeName (char *template)
9720 {
9721     time_t clock;
9722     struct tm *tm;
9723     static char buf[MSG_SIZ];
9724     char *p = buf;
9725     int i;
9726
9727     clock = time((time_t *)NULL);
9728     tm = localtime(&clock);
9729
9730     while(*p++ = *template++) if(p[-1] == '%') {
9731         switch(*template++) {
9732           case 0:   *p = 0; return buf;
9733           case 'Y': i = tm->tm_year+1900; break;
9734           case 'y': i = tm->tm_year-100; break;
9735           case 'M': i = tm->tm_mon+1; break;
9736           case 'd': i = tm->tm_mday; break;
9737           case 'h': i = tm->tm_hour; break;
9738           case 'm': i = tm->tm_min; break;
9739           case 's': i = tm->tm_sec; break;
9740           default:  i = 0;
9741         }
9742         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9743     }
9744     return buf;
9745 }
9746
9747 int
9748 CountPlayers (char *p)
9749 {
9750     int n = 0;
9751     while(p = strchr(p, '\n')) p++, n++; // count participants
9752     return n;
9753 }
9754
9755 FILE *
9756 WriteTourneyFile (char *results, FILE *f)
9757 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9758     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9759     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9760         // create a file with tournament description
9761         fprintf(f, "-participants {%s}\n", appData.participants);
9762         fprintf(f, "-seedBase %d\n", appData.seedBase);
9763         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9764         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9765         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9766         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9767         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9768         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9769         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9770         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9771         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9772         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9773         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9774         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9775         if(searchTime > 0)
9776                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9777         else {
9778                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9779                 fprintf(f, "-tc %s\n", appData.timeControl);
9780                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9781         }
9782         fprintf(f, "-results \"%s\"\n", results);
9783     }
9784     return f;
9785 }
9786
9787 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9788
9789 void
9790 Substitute (char *participants, int expunge)
9791 {
9792     int i, changed, changes=0, nPlayers=0;
9793     char *p, *q, *r, buf[MSG_SIZ];
9794     if(participants == NULL) return;
9795     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9796     r = p = participants; q = appData.participants;
9797     while(*p && *p == *q) {
9798         if(*p == '\n') r = p+1, nPlayers++;
9799         p++; q++;
9800     }
9801     if(*p) { // difference
9802         while(*p && *p++ != '\n');
9803         while(*q && *q++ != '\n');
9804       changed = nPlayers;
9805         changes = 1 + (strcmp(p, q) != 0);
9806     }
9807     if(changes == 1) { // a single engine mnemonic was changed
9808         q = r; while(*q) nPlayers += (*q++ == '\n');
9809         p = buf; while(*r && (*p = *r++) != '\n') p++;
9810         *p = NULLCHAR;
9811         NamesToList(firstChessProgramNames, command, mnemonic);
9812         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9813         if(mnemonic[i]) { // The substitute is valid
9814             FILE *f;
9815             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9816                 flock(fileno(f), LOCK_EX);
9817                 ParseArgsFromFile(f);
9818                 fseek(f, 0, SEEK_SET);
9819                 FREE(appData.participants); appData.participants = participants;
9820                 if(expunge) { // erase results of replaced engine
9821                     int len = strlen(appData.results), w, b, dummy;
9822                     for(i=0; i<len; i++) {
9823                         Pairing(i, nPlayers, &w, &b, &dummy);
9824                         if((w == changed || b == changed) && appData.results[i] == '*') {
9825                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9826                             fclose(f);
9827                             return;
9828                         }
9829                     }
9830                     for(i=0; i<len; i++) {
9831                         Pairing(i, nPlayers, &w, &b, &dummy);
9832                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9833                     }
9834                 }
9835                 WriteTourneyFile(appData.results, f);
9836                 fclose(f); // release lock
9837                 return;
9838             }
9839         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9840     }
9841     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9842     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9843     free(participants);
9844     return;
9845 }
9846
9847 int
9848 CreateTourney (char *name)
9849 {
9850         FILE *f;
9851         if(matchMode && strcmp(name, appData.tourneyFile)) {
9852              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9853         }
9854         if(name[0] == NULLCHAR) {
9855             if(appData.participants[0])
9856                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9857             return 0;
9858         }
9859         f = fopen(name, "r");
9860         if(f) { // file exists
9861             ASSIGN(appData.tourneyFile, name);
9862             ParseArgsFromFile(f); // parse it
9863         } else {
9864             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9865             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9866                 DisplayError(_("Not enough participants"), 0);
9867                 return 0;
9868             }
9869             ASSIGN(appData.tourneyFile, name);
9870             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9871             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9872         }
9873         fclose(f);
9874         appData.noChessProgram = FALSE;
9875         appData.clockMode = TRUE;
9876         SetGNUMode();
9877         return 1;
9878 }
9879
9880 void
9881 NamesToList (char *names, char **engineList, char **engineMnemonic)
9882 {
9883     char buf[MSG_SIZ], *p, *q;
9884     int i=1;
9885     while(*names) {
9886         p = names; q = buf;
9887         while(*p && *p != '\n') *q++ = *p++;
9888         *q = 0;
9889         if(engineList[i]) free(engineList[i]);
9890         engineList[i] = strdup(buf);
9891         if(*p == '\n') p++;
9892         TidyProgramName(engineList[i], "localhost", buf);
9893         if(engineMnemonic[i]) free(engineMnemonic[i]);
9894         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9895             strcat(buf, " (");
9896             sscanf(q + 8, "%s", buf + strlen(buf));
9897             strcat(buf, ")");
9898         }
9899         engineMnemonic[i] = strdup(buf);
9900         names = p; i++;
9901       if(i > MAXENGINES - 2) break;
9902     }
9903     engineList[i] = engineMnemonic[i] = NULL;
9904 }
9905
9906 // following implemented as macro to avoid type limitations
9907 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9908
9909 void
9910 SwapEngines (int n)
9911 {   // swap settings for first engine and other engine (so far only some selected options)
9912     int h;
9913     char *p;
9914     if(n == 0) return;
9915     SWAP(directory, p)
9916     SWAP(chessProgram, p)
9917     SWAP(isUCI, h)
9918     SWAP(hasOwnBookUCI, h)
9919     SWAP(protocolVersion, h)
9920     SWAP(reuse, h)
9921     SWAP(scoreIsAbsolute, h)
9922     SWAP(timeOdds, h)
9923     SWAP(logo, p)
9924     SWAP(pgnName, p)
9925     SWAP(pvSAN, h)
9926     SWAP(engOptions, p)
9927 }
9928
9929 int
9930 SetPlayer (int player, char *p)
9931 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9932     int i;
9933     char buf[MSG_SIZ], *engineName;
9934     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9935     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9936     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9937     if(mnemonic[i]) {
9938         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9939         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9940         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
9941         ParseArgsFromString(buf);
9942     }
9943     free(engineName);
9944     return i;
9945 }
9946
9947 char *recentEngines;
9948
9949 void
9950 RecentEngineEvent (int nr)
9951 {
9952     int n;
9953 //    SwapEngines(1); // bump first to second
9954 //    ReplaceEngine(&second, 1); // and load it there
9955     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9956     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
9957     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
9958         ReplaceEngine(&first, 0);
9959         FloatToFront(&appData.recentEngineList, command[n]);
9960     }
9961 }
9962
9963 int
9964 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9965 {   // determine players from game number
9966     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9967
9968     if(appData.tourneyType == 0) {
9969         roundsPerCycle = (nPlayers - 1) | 1;
9970         pairingsPerRound = nPlayers / 2;
9971     } else if(appData.tourneyType > 0) {
9972         roundsPerCycle = nPlayers - appData.tourneyType;
9973         pairingsPerRound = appData.tourneyType;
9974     }
9975     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9976     gamesPerCycle = gamesPerRound * roundsPerCycle;
9977     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9978     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9979     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9980     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9981     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9982     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9983
9984     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9985     if(appData.roundSync) *syncInterval = gamesPerRound;
9986
9987     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9988
9989     if(appData.tourneyType == 0) {
9990         if(curPairing == (nPlayers-1)/2 ) {
9991             *whitePlayer = curRound;
9992             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9993         } else {
9994             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9995             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9996             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9997             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9998         }
9999     } else if(appData.tourneyType > 1) {
10000         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10001         *whitePlayer = curRound + appData.tourneyType;
10002     } else if(appData.tourneyType > 0) {
10003         *whitePlayer = curPairing;
10004         *blackPlayer = curRound + appData.tourneyType;
10005     }
10006
10007     // take care of white/black alternation per round. 
10008     // For cycles and games this is already taken care of by default, derived from matchGame!
10009     return curRound & 1;
10010 }
10011
10012 int
10013 NextTourneyGame (int nr, int *swapColors)
10014 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10015     char *p, *q;
10016     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10017     FILE *tf;
10018     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10019     tf = fopen(appData.tourneyFile, "r");
10020     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10021     ParseArgsFromFile(tf); fclose(tf);
10022     InitTimeControls(); // TC might be altered from tourney file
10023
10024     nPlayers = CountPlayers(appData.participants); // count participants
10025     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10026     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10027
10028     if(syncInterval) {
10029         p = q = appData.results;
10030         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10031         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10032             DisplayMessage(_("Waiting for other game(s)"),"");
10033             waitingForGame = TRUE;
10034             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10035             return 0;
10036         }
10037         waitingForGame = FALSE;
10038     }
10039
10040     if(appData.tourneyType < 0) {
10041         if(nr>=0 && !pairingReceived) {
10042             char buf[1<<16];
10043             if(pairing.pr == NoProc) {
10044                 if(!appData.pairingEngine[0]) {
10045                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10046                     return 0;
10047                 }
10048                 StartChessProgram(&pairing); // starts the pairing engine
10049             }
10050             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10051             SendToProgram(buf, &pairing);
10052             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10053             SendToProgram(buf, &pairing);
10054             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10055         }
10056         pairingReceived = 0;                              // ... so we continue here 
10057         *swapColors = 0;
10058         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10059         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10060         matchGame = 1; roundNr = nr / syncInterval + 1;
10061     }
10062
10063     if(first.pr != NoProc && second.pr != NoProc) return 1; // engines already loaded
10064
10065     // redefine engines, engine dir, etc.
10066     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10067     if(first.pr == NoProc || nr < 0) {
10068       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10069       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10070     }
10071     if(second.pr == NoProc) {
10072       SwapEngines(1);
10073       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10074       SwapEngines(1);         // and make that valid for second engine by swapping
10075       InitEngine(&second, 1);
10076     }
10077     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10078     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10079     return 1;
10080 }
10081
10082 void
10083 NextMatchGame ()
10084 {   // performs game initialization that does not invoke engines, and then tries to start the game
10085     int res, firstWhite, swapColors = 0;
10086     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10087     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10088     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10089     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10090     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10091     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10092     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10093     Reset(FALSE, first.pr != NoProc);
10094     res = LoadGameOrPosition(matchGame); // setup game
10095     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10096     if(!res) return; // abort when bad game/pos file
10097     TwoMachinesEvent();
10098 }
10099
10100 void
10101 UserAdjudicationEvent (int result)
10102 {
10103     ChessMove gameResult = GameIsDrawn;
10104
10105     if( result > 0 ) {
10106         gameResult = WhiteWins;
10107     }
10108     else if( result < 0 ) {
10109         gameResult = BlackWins;
10110     }
10111
10112     if( gameMode == TwoMachinesPlay ) {
10113         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10114     }
10115 }
10116
10117
10118 // [HGM] save: calculate checksum of game to make games easily identifiable
10119 int
10120 StringCheckSum (char *s)
10121 {
10122         int i = 0;
10123         if(s==NULL) return 0;
10124         while(*s) i = i*259 + *s++;
10125         return i;
10126 }
10127
10128 int
10129 GameCheckSum ()
10130 {
10131         int i, sum=0;
10132         for(i=backwardMostMove; i<forwardMostMove; i++) {
10133                 sum += pvInfoList[i].depth;
10134                 sum += StringCheckSum(parseList[i]);
10135                 sum += StringCheckSum(commentList[i]);
10136                 sum *= 261;
10137         }
10138         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10139         return sum + StringCheckSum(commentList[i]);
10140 } // end of save patch
10141
10142 void
10143 GameEnds (ChessMove result, char *resultDetails, int whosays)
10144 {
10145     GameMode nextGameMode;
10146     int isIcsGame;
10147     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10148
10149     if(endingGame) return; /* [HGM] crash: forbid recursion */
10150     endingGame = 1;
10151     if(twoBoards) { // [HGM] dual: switch back to one board
10152         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10153         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10154     }
10155     if (appData.debugMode) {
10156       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10157               result, resultDetails ? resultDetails : "(null)", whosays);
10158     }
10159
10160     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10161
10162     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10163         /* If we are playing on ICS, the server decides when the
10164            game is over, but the engine can offer to draw, claim
10165            a draw, or resign.
10166          */
10167 #if ZIPPY
10168         if (appData.zippyPlay && first.initDone) {
10169             if (result == GameIsDrawn) {
10170                 /* In case draw still needs to be claimed */
10171                 SendToICS(ics_prefix);
10172                 SendToICS("draw\n");
10173             } else if (StrCaseStr(resultDetails, "resign")) {
10174                 SendToICS(ics_prefix);
10175                 SendToICS("resign\n");
10176             }
10177         }
10178 #endif
10179         endingGame = 0; /* [HGM] crash */
10180         return;
10181     }
10182
10183     /* If we're loading the game from a file, stop */
10184     if (whosays == GE_FILE) {
10185       (void) StopLoadGameTimer();
10186       gameFileFP = NULL;
10187     }
10188
10189     /* Cancel draw offers */
10190     first.offeredDraw = second.offeredDraw = 0;
10191
10192     /* If this is an ICS game, only ICS can really say it's done;
10193        if not, anyone can. */
10194     isIcsGame = (gameMode == IcsPlayingWhite ||
10195                  gameMode == IcsPlayingBlack ||
10196                  gameMode == IcsObserving    ||
10197                  gameMode == IcsExamining);
10198
10199     if (!isIcsGame || whosays == GE_ICS) {
10200         /* OK -- not an ICS game, or ICS said it was done */
10201         StopClocks();
10202         if (!isIcsGame && !appData.noChessProgram)
10203           SetUserThinkingEnables();
10204
10205         /* [HGM] if a machine claims the game end we verify this claim */
10206         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10207             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10208                 char claimer;
10209                 ChessMove trueResult = (ChessMove) -1;
10210
10211                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10212                                             first.twoMachinesColor[0] :
10213                                             second.twoMachinesColor[0] ;
10214
10215                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10216                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10217                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10218                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10219                 } else
10220                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10221                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10222                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10223                 } else
10224                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10225                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10226                 }
10227
10228                 // now verify win claims, but not in drop games, as we don't understand those yet
10229                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10230                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10231                     (result == WhiteWins && claimer == 'w' ||
10232                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10233                       if (appData.debugMode) {
10234                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10235                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10236                       }
10237                       if(result != trueResult) {
10238                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10239                               result = claimer == 'w' ? BlackWins : WhiteWins;
10240                               resultDetails = buf;
10241                       }
10242                 } else
10243                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10244                     && (forwardMostMove <= backwardMostMove ||
10245                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10246                         (claimer=='b')==(forwardMostMove&1))
10247                                                                                   ) {
10248                       /* [HGM] verify: draws that were not flagged are false claims */
10249                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10250                       result = claimer == 'w' ? BlackWins : WhiteWins;
10251                       resultDetails = buf;
10252                 }
10253                 /* (Claiming a loss is accepted no questions asked!) */
10254             }
10255             /* [HGM] bare: don't allow bare King to win */
10256             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10257                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10258                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10259                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10260                && result != GameIsDrawn)
10261             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10262                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10263                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10264                         if(p >= 0 && p <= (int)WhiteKing) k++;
10265                 }
10266                 if (appData.debugMode) {
10267                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10268                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10269                 }
10270                 if(k <= 1) {
10271                         result = GameIsDrawn;
10272                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10273                         resultDetails = buf;
10274                 }
10275             }
10276         }
10277
10278
10279         if(serverMoves != NULL && !loadFlag) { char c = '=';
10280             if(result==WhiteWins) c = '+';
10281             if(result==BlackWins) c = '-';
10282             if(resultDetails != NULL)
10283                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10284         }
10285         if (resultDetails != NULL) {
10286             gameInfo.result = result;
10287             gameInfo.resultDetails = StrSave(resultDetails);
10288
10289             /* display last move only if game was not loaded from file */
10290             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10291                 DisplayMove(currentMove - 1);
10292
10293             if (forwardMostMove != 0) {
10294                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10295                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10296                                                                 ) {
10297                     if (*appData.saveGameFile != NULLCHAR) {
10298                         SaveGameToFile(appData.saveGameFile, TRUE);
10299                     } else if (appData.autoSaveGames) {
10300                         AutoSaveGame();
10301                     }
10302                     if (*appData.savePositionFile != NULLCHAR) {
10303                         SavePositionToFile(appData.savePositionFile);
10304                     }
10305                 }
10306             }
10307
10308             /* Tell program how game ended in case it is learning */
10309             /* [HGM] Moved this to after saving the PGN, just in case */
10310             /* engine died and we got here through time loss. In that */
10311             /* case we will get a fatal error writing the pipe, which */
10312             /* would otherwise lose us the PGN.                       */
10313             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10314             /* output during GameEnds should never be fatal anymore   */
10315             if (gameMode == MachinePlaysWhite ||
10316                 gameMode == MachinePlaysBlack ||
10317                 gameMode == TwoMachinesPlay ||
10318                 gameMode == IcsPlayingWhite ||
10319                 gameMode == IcsPlayingBlack ||
10320                 gameMode == BeginningOfGame) {
10321                 char buf[MSG_SIZ];
10322                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10323                         resultDetails);
10324                 if (first.pr != NoProc) {
10325                     SendToProgram(buf, &first);
10326                 }
10327                 if (second.pr != NoProc &&
10328                     gameMode == TwoMachinesPlay) {
10329                     SendToProgram(buf, &second);
10330                 }
10331             }
10332         }
10333
10334         if (appData.icsActive) {
10335             if (appData.quietPlay &&
10336                 (gameMode == IcsPlayingWhite ||
10337                  gameMode == IcsPlayingBlack)) {
10338                 SendToICS(ics_prefix);
10339                 SendToICS("set shout 1\n");
10340             }
10341             nextGameMode = IcsIdle;
10342             ics_user_moved = FALSE;
10343             /* clean up premove.  It's ugly when the game has ended and the
10344              * premove highlights are still on the board.
10345              */
10346             if (gotPremove) {
10347               gotPremove = FALSE;
10348               ClearPremoveHighlights();
10349               DrawPosition(FALSE, boards[currentMove]);
10350             }
10351             if (whosays == GE_ICS) {
10352                 switch (result) {
10353                 case WhiteWins:
10354                     if (gameMode == IcsPlayingWhite)
10355                         PlayIcsWinSound();
10356                     else if(gameMode == IcsPlayingBlack)
10357                         PlayIcsLossSound();
10358                     break;
10359                 case BlackWins:
10360                     if (gameMode == IcsPlayingBlack)
10361                         PlayIcsWinSound();
10362                     else if(gameMode == IcsPlayingWhite)
10363                         PlayIcsLossSound();
10364                     break;
10365                 case GameIsDrawn:
10366                     PlayIcsDrawSound();
10367                     break;
10368                 default:
10369                     PlayIcsUnfinishedSound();
10370                 }
10371             }
10372         } else if (gameMode == EditGame ||
10373                    gameMode == PlayFromGameFile ||
10374                    gameMode == AnalyzeMode ||
10375                    gameMode == AnalyzeFile) {
10376             nextGameMode = gameMode;
10377         } else {
10378             nextGameMode = EndOfGame;
10379         }
10380         pausing = FALSE;
10381         ModeHighlight();
10382     } else {
10383         nextGameMode = gameMode;
10384     }
10385
10386     if (appData.noChessProgram) {
10387         gameMode = nextGameMode;
10388         ModeHighlight();
10389         endingGame = 0; /* [HGM] crash */
10390         return;
10391     }
10392
10393     if (first.reuse) {
10394         /* Put first chess program into idle state */
10395         if (first.pr != NoProc &&
10396             (gameMode == MachinePlaysWhite ||
10397              gameMode == MachinePlaysBlack ||
10398              gameMode == TwoMachinesPlay ||
10399              gameMode == IcsPlayingWhite ||
10400              gameMode == IcsPlayingBlack ||
10401              gameMode == BeginningOfGame)) {
10402             SendToProgram("force\n", &first);
10403             if (first.usePing) {
10404               char buf[MSG_SIZ];
10405               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10406               SendToProgram(buf, &first);
10407             }
10408         }
10409     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10410         /* Kill off first chess program */
10411         if (first.isr != NULL)
10412           RemoveInputSource(first.isr);
10413         first.isr = NULL;
10414
10415         if (first.pr != NoProc) {
10416             ExitAnalyzeMode();
10417             DoSleep( appData.delayBeforeQuit );
10418             SendToProgram("quit\n", &first);
10419             DoSleep( appData.delayAfterQuit );
10420             DestroyChildProcess(first.pr, first.useSigterm);
10421         }
10422         first.pr = NoProc;
10423     }
10424     if (second.reuse) {
10425         /* Put second chess program into idle state */
10426         if (second.pr != NoProc &&
10427             gameMode == TwoMachinesPlay) {
10428             SendToProgram("force\n", &second);
10429             if (second.usePing) {
10430               char buf[MSG_SIZ];
10431               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10432               SendToProgram(buf, &second);
10433             }
10434         }
10435     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10436         /* Kill off second chess program */
10437         if (second.isr != NULL)
10438           RemoveInputSource(second.isr);
10439         second.isr = NULL;
10440
10441         if (second.pr != NoProc) {
10442             DoSleep( appData.delayBeforeQuit );
10443             SendToProgram("quit\n", &second);
10444             DoSleep( appData.delayAfterQuit );
10445             DestroyChildProcess(second.pr, second.useSigterm);
10446         }
10447         second.pr = NoProc;
10448     }
10449
10450     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10451         char resChar = '=';
10452         switch (result) {
10453         case WhiteWins:
10454           resChar = '+';
10455           if (first.twoMachinesColor[0] == 'w') {
10456             first.matchWins++;
10457           } else {
10458             second.matchWins++;
10459           }
10460           break;
10461         case BlackWins:
10462           resChar = '-';
10463           if (first.twoMachinesColor[0] == 'b') {
10464             first.matchWins++;
10465           } else {
10466             second.matchWins++;
10467           }
10468           break;
10469         case GameUnfinished:
10470           resChar = ' ';
10471         default:
10472           break;
10473         }
10474
10475         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10476         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10477             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10478             ReserveGame(nextGame, resChar); // sets nextGame
10479             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10480             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10481         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10482
10483         if (nextGame <= appData.matchGames && !abortMatch) {
10484             gameMode = nextGameMode;
10485             matchGame = nextGame; // this will be overruled in tourney mode!
10486             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10487             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10488             endingGame = 0; /* [HGM] crash */
10489             return;
10490         } else {
10491             gameMode = nextGameMode;
10492             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10493                      first.tidy, second.tidy,
10494                      first.matchWins, second.matchWins,
10495                      appData.matchGames - (first.matchWins + second.matchWins));
10496             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10497             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10498             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10499             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10500                 first.twoMachinesColor = "black\n";
10501                 second.twoMachinesColor = "white\n";
10502             } else {
10503                 first.twoMachinesColor = "white\n";
10504                 second.twoMachinesColor = "black\n";
10505             }
10506         }
10507     }
10508     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10509         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10510       ExitAnalyzeMode();
10511     gameMode = nextGameMode;
10512     ModeHighlight();
10513     endingGame = 0;  /* [HGM] crash */
10514     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10515         if(matchMode == TRUE) { // match through command line: exit with or without popup
10516             if(ranking) {
10517                 ToNrEvent(forwardMostMove);
10518                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10519                 else ExitEvent(0);
10520             } else DisplayFatalError(buf, 0, 0);
10521         } else { // match through menu; just stop, with or without popup
10522             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10523             ModeHighlight();
10524             if(ranking){
10525                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10526             } else DisplayNote(buf);
10527       }
10528       if(ranking) free(ranking);
10529     }
10530 }
10531
10532 /* Assumes program was just initialized (initString sent).
10533    Leaves program in force mode. */
10534 void
10535 FeedMovesToProgram (ChessProgramState *cps, int upto)
10536 {
10537     int i;
10538
10539     if (appData.debugMode)
10540       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10541               startedFromSetupPosition ? "position and " : "",
10542               backwardMostMove, upto, cps->which);
10543     if(currentlyInitializedVariant != gameInfo.variant) {
10544       char buf[MSG_SIZ];
10545         // [HGM] variantswitch: make engine aware of new variant
10546         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10547                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10548         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10549         SendToProgram(buf, cps);
10550         currentlyInitializedVariant = gameInfo.variant;
10551     }
10552     SendToProgram("force\n", cps);
10553     if (startedFromSetupPosition) {
10554         SendBoard(cps, backwardMostMove);
10555     if (appData.debugMode) {
10556         fprintf(debugFP, "feedMoves\n");
10557     }
10558     }
10559     for (i = backwardMostMove; i < upto; i++) {
10560         SendMoveToProgram(i, cps);
10561     }
10562 }
10563
10564
10565 int
10566 ResurrectChessProgram ()
10567 {
10568      /* The chess program may have exited.
10569         If so, restart it and feed it all the moves made so far. */
10570     static int doInit = 0;
10571
10572     if (appData.noChessProgram) return 1;
10573
10574     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10575         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10576         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10577         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10578     } else {
10579         if (first.pr != NoProc) return 1;
10580         StartChessProgram(&first);
10581     }
10582     InitChessProgram(&first, FALSE);
10583     FeedMovesToProgram(&first, currentMove);
10584
10585     if (!first.sendTime) {
10586         /* can't tell gnuchess what its clock should read,
10587            so we bow to its notion. */
10588         ResetClocks();
10589         timeRemaining[0][currentMove] = whiteTimeRemaining;
10590         timeRemaining[1][currentMove] = blackTimeRemaining;
10591     }
10592
10593     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10594                 appData.icsEngineAnalyze) && first.analysisSupport) {
10595       SendToProgram("analyze\n", &first);
10596       first.analyzing = TRUE;
10597     }
10598     return 1;
10599 }
10600
10601 /*
10602  * Button procedures
10603  */
10604 void
10605 Reset (int redraw, int init)
10606 {
10607     int i;
10608
10609     if (appData.debugMode) {
10610         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10611                 redraw, init, gameMode);
10612     }
10613     CleanupTail(); // [HGM] vari: delete any stored variations
10614     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10615     pausing = pauseExamInvalid = FALSE;
10616     startedFromSetupPosition = blackPlaysFirst = FALSE;
10617     firstMove = TRUE;
10618     whiteFlag = blackFlag = FALSE;
10619     userOfferedDraw = FALSE;
10620     hintRequested = bookRequested = FALSE;
10621     first.maybeThinking = FALSE;
10622     second.maybeThinking = FALSE;
10623     first.bookSuspend = FALSE; // [HGM] book
10624     second.bookSuspend = FALSE;
10625     thinkOutput[0] = NULLCHAR;
10626     lastHint[0] = NULLCHAR;
10627     ClearGameInfo(&gameInfo);
10628     gameInfo.variant = StringToVariant(appData.variant);
10629     ics_user_moved = ics_clock_paused = FALSE;
10630     ics_getting_history = H_FALSE;
10631     ics_gamenum = -1;
10632     white_holding[0] = black_holding[0] = NULLCHAR;
10633     ClearProgramStats();
10634     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10635
10636     ResetFrontEnd();
10637     ClearHighlights();
10638     flipView = appData.flipView;
10639     ClearPremoveHighlights();
10640     gotPremove = FALSE;
10641     alarmSounded = FALSE;
10642
10643     GameEnds(EndOfFile, NULL, GE_PLAYER);
10644     if(appData.serverMovesName != NULL) {
10645         /* [HGM] prepare to make moves file for broadcasting */
10646         clock_t t = clock();
10647         if(serverMoves != NULL) fclose(serverMoves);
10648         serverMoves = fopen(appData.serverMovesName, "r");
10649         if(serverMoves != NULL) {
10650             fclose(serverMoves);
10651             /* delay 15 sec before overwriting, so all clients can see end */
10652             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10653         }
10654         serverMoves = fopen(appData.serverMovesName, "w");
10655     }
10656
10657     ExitAnalyzeMode();
10658     gameMode = BeginningOfGame;
10659     ModeHighlight();
10660     if(appData.icsActive) gameInfo.variant = VariantNormal;
10661     currentMove = forwardMostMove = backwardMostMove = 0;
10662     MarkTargetSquares(1);
10663     InitPosition(redraw);
10664     for (i = 0; i < MAX_MOVES; i++) {
10665         if (commentList[i] != NULL) {
10666             free(commentList[i]);
10667             commentList[i] = NULL;
10668         }
10669     }
10670     ResetClocks();
10671     timeRemaining[0][0] = whiteTimeRemaining;
10672     timeRemaining[1][0] = blackTimeRemaining;
10673
10674     if (first.pr == NoProc) {
10675         StartChessProgram(&first);
10676     }
10677     if (init) {
10678             InitChessProgram(&first, startedFromSetupPosition);
10679     }
10680     DisplayTitle("");
10681     DisplayMessage("", "");
10682     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10683     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10684 }
10685
10686 void
10687 AutoPlayGameLoop ()
10688 {
10689     for (;;) {
10690         if (!AutoPlayOneMove())
10691           return;
10692         if (matchMode || appData.timeDelay == 0)
10693           continue;
10694         if (appData.timeDelay < 0)
10695           return;
10696         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10697         break;
10698     }
10699 }
10700
10701
10702 int
10703 AutoPlayOneMove ()
10704 {
10705     int fromX, fromY, toX, toY;
10706
10707     if (appData.debugMode) {
10708       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10709     }
10710
10711     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10712       return FALSE;
10713
10714     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10715       pvInfoList[currentMove].depth = programStats.depth;
10716       pvInfoList[currentMove].score = programStats.score;
10717       pvInfoList[currentMove].time  = 0;
10718       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10719     }
10720
10721     if (currentMove >= forwardMostMove) {
10722       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10723 //      gameMode = EndOfGame;
10724 //      ModeHighlight();
10725
10726       /* [AS] Clear current move marker at the end of a game */
10727       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10728
10729       return FALSE;
10730     }
10731
10732     toX = moveList[currentMove][2] - AAA;
10733     toY = moveList[currentMove][3] - ONE;
10734
10735     if (moveList[currentMove][1] == '@') {
10736         if (appData.highlightLastMove) {
10737             SetHighlights(-1, -1, toX, toY);
10738         }
10739     } else {
10740         fromX = moveList[currentMove][0] - AAA;
10741         fromY = moveList[currentMove][1] - ONE;
10742
10743         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10744
10745         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10746
10747         if (appData.highlightLastMove) {
10748             SetHighlights(fromX, fromY, toX, toY);
10749         }
10750     }
10751     DisplayMove(currentMove);
10752     SendMoveToProgram(currentMove++, &first);
10753     DisplayBothClocks();
10754     DrawPosition(FALSE, boards[currentMove]);
10755     // [HGM] PV info: always display, routine tests if empty
10756     DisplayComment(currentMove - 1, commentList[currentMove]);
10757     return TRUE;
10758 }
10759
10760
10761 int
10762 LoadGameOneMove (ChessMove readAhead)
10763 {
10764     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10765     char promoChar = NULLCHAR;
10766     ChessMove moveType;
10767     char move[MSG_SIZ];
10768     char *p, *q;
10769
10770     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10771         gameMode != AnalyzeMode && gameMode != Training) {
10772         gameFileFP = NULL;
10773         return FALSE;
10774     }
10775
10776     yyboardindex = forwardMostMove;
10777     if (readAhead != EndOfFile) {
10778       moveType = readAhead;
10779     } else {
10780       if (gameFileFP == NULL)
10781           return FALSE;
10782       moveType = (ChessMove) Myylex();
10783     }
10784
10785     done = FALSE;
10786     switch (moveType) {
10787       case Comment:
10788         if (appData.debugMode)
10789           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10790         p = yy_text;
10791
10792         /* append the comment but don't display it */
10793         AppendComment(currentMove, p, FALSE);
10794         return TRUE;
10795
10796       case WhiteCapturesEnPassant:
10797       case BlackCapturesEnPassant:
10798       case WhitePromotion:
10799       case BlackPromotion:
10800       case WhiteNonPromotion:
10801       case BlackNonPromotion:
10802       case NormalMove:
10803       case WhiteKingSideCastle:
10804       case WhiteQueenSideCastle:
10805       case BlackKingSideCastle:
10806       case BlackQueenSideCastle:
10807       case WhiteKingSideCastleWild:
10808       case WhiteQueenSideCastleWild:
10809       case BlackKingSideCastleWild:
10810       case BlackQueenSideCastleWild:
10811       /* PUSH Fabien */
10812       case WhiteHSideCastleFR:
10813       case WhiteASideCastleFR:
10814       case BlackHSideCastleFR:
10815       case BlackASideCastleFR:
10816       /* POP Fabien */
10817         if (appData.debugMode)
10818           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10819         fromX = currentMoveString[0] - AAA;
10820         fromY = currentMoveString[1] - ONE;
10821         toX = currentMoveString[2] - AAA;
10822         toY = currentMoveString[3] - ONE;
10823         promoChar = currentMoveString[4];
10824         break;
10825
10826       case WhiteDrop:
10827       case BlackDrop:
10828         if (appData.debugMode)
10829           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10830         fromX = moveType == WhiteDrop ?
10831           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10832         (int) CharToPiece(ToLower(currentMoveString[0]));
10833         fromY = DROP_RANK;
10834         toX = currentMoveString[2] - AAA;
10835         toY = currentMoveString[3] - ONE;
10836         break;
10837
10838       case WhiteWins:
10839       case BlackWins:
10840       case GameIsDrawn:
10841       case GameUnfinished:
10842         if (appData.debugMode)
10843           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10844         p = strchr(yy_text, '{');
10845         if (p == NULL) p = strchr(yy_text, '(');
10846         if (p == NULL) {
10847             p = yy_text;
10848             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10849         } else {
10850             q = strchr(p, *p == '{' ? '}' : ')');
10851             if (q != NULL) *q = NULLCHAR;
10852             p++;
10853         }
10854         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10855         GameEnds(moveType, p, GE_FILE);
10856         done = TRUE;
10857         if (cmailMsgLoaded) {
10858             ClearHighlights();
10859             flipView = WhiteOnMove(currentMove);
10860             if (moveType == GameUnfinished) flipView = !flipView;
10861             if (appData.debugMode)
10862               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10863         }
10864         break;
10865
10866       case EndOfFile:
10867         if (appData.debugMode)
10868           fprintf(debugFP, "Parser hit end of file\n");
10869         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10870           case MT_NONE:
10871           case MT_CHECK:
10872             break;
10873           case MT_CHECKMATE:
10874           case MT_STAINMATE:
10875             if (WhiteOnMove(currentMove)) {
10876                 GameEnds(BlackWins, "Black mates", GE_FILE);
10877             } else {
10878                 GameEnds(WhiteWins, "White mates", GE_FILE);
10879             }
10880             break;
10881           case MT_STALEMATE:
10882             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10883             break;
10884         }
10885         done = TRUE;
10886         break;
10887
10888       case MoveNumberOne:
10889         if (lastLoadGameStart == GNUChessGame) {
10890             /* GNUChessGames have numbers, but they aren't move numbers */
10891             if (appData.debugMode)
10892               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10893                       yy_text, (int) moveType);
10894             return LoadGameOneMove(EndOfFile); /* tail recursion */
10895         }
10896         /* else fall thru */
10897
10898       case XBoardGame:
10899       case GNUChessGame:
10900       case PGNTag:
10901         /* Reached start of next game in file */
10902         if (appData.debugMode)
10903           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10904         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10905           case MT_NONE:
10906           case MT_CHECK:
10907             break;
10908           case MT_CHECKMATE:
10909           case MT_STAINMATE:
10910             if (WhiteOnMove(currentMove)) {
10911                 GameEnds(BlackWins, "Black mates", GE_FILE);
10912             } else {
10913                 GameEnds(WhiteWins, "White mates", GE_FILE);
10914             }
10915             break;
10916           case MT_STALEMATE:
10917             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10918             break;
10919         }
10920         done = TRUE;
10921         break;
10922
10923       case PositionDiagram:     /* should not happen; ignore */
10924       case ElapsedTime:         /* ignore */
10925       case NAG:                 /* ignore */
10926         if (appData.debugMode)
10927           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10928                   yy_text, (int) moveType);
10929         return LoadGameOneMove(EndOfFile); /* tail recursion */
10930
10931       case IllegalMove:
10932         if (appData.testLegality) {
10933             if (appData.debugMode)
10934               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10935             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10936                     (forwardMostMove / 2) + 1,
10937                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10938             DisplayError(move, 0);
10939             done = TRUE;
10940         } else {
10941             if (appData.debugMode)
10942               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10943                       yy_text, currentMoveString);
10944             fromX = currentMoveString[0] - AAA;
10945             fromY = currentMoveString[1] - ONE;
10946             toX = currentMoveString[2] - AAA;
10947             toY = currentMoveString[3] - ONE;
10948             promoChar = currentMoveString[4];
10949         }
10950         break;
10951
10952       case AmbiguousMove:
10953         if (appData.debugMode)
10954           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10955         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10956                 (forwardMostMove / 2) + 1,
10957                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10958         DisplayError(move, 0);
10959         done = TRUE;
10960         break;
10961
10962       default:
10963       case ImpossibleMove:
10964         if (appData.debugMode)
10965           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10966         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10967                 (forwardMostMove / 2) + 1,
10968                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10969         DisplayError(move, 0);
10970         done = TRUE;
10971         break;
10972     }
10973
10974     if (done) {
10975         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10976             DrawPosition(FALSE, boards[currentMove]);
10977             DisplayBothClocks();
10978             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10979               DisplayComment(currentMove - 1, commentList[currentMove]);
10980         }
10981         (void) StopLoadGameTimer();
10982         gameFileFP = NULL;
10983         cmailOldMove = forwardMostMove;
10984         return FALSE;
10985     } else {
10986         /* currentMoveString is set as a side-effect of yylex */
10987
10988         thinkOutput[0] = NULLCHAR;
10989         MakeMove(fromX, fromY, toX, toY, promoChar);
10990         currentMove = forwardMostMove;
10991         return TRUE;
10992     }
10993 }
10994
10995 /* Load the nth game from the given file */
10996 int
10997 LoadGameFromFile (char *filename, int n, char *title, int useList)
10998 {
10999     FILE *f;
11000     char buf[MSG_SIZ];
11001
11002     if (strcmp(filename, "-") == 0) {
11003         f = stdin;
11004         title = "stdin";
11005     } else {
11006         f = fopen(filename, "rb");
11007         if (f == NULL) {
11008           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11009             DisplayError(buf, errno);
11010             return FALSE;
11011         }
11012     }
11013     if (fseek(f, 0, 0) == -1) {
11014         /* f is not seekable; probably a pipe */
11015         useList = FALSE;
11016     }
11017     if (useList && n == 0) {
11018         int error = GameListBuild(f);
11019         if (error) {
11020             DisplayError(_("Cannot build game list"), error);
11021         } else if (!ListEmpty(&gameList) &&
11022                    ((ListGame *) gameList.tailPred)->number > 1) {
11023             GameListPopUp(f, title);
11024             return TRUE;
11025         }
11026         GameListDestroy();
11027         n = 1;
11028     }
11029     if (n == 0) n = 1;
11030     return LoadGame(f, n, title, FALSE);
11031 }
11032
11033
11034 void
11035 MakeRegisteredMove ()
11036 {
11037     int fromX, fromY, toX, toY;
11038     char promoChar;
11039     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11040         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11041           case CMAIL_MOVE:
11042           case CMAIL_DRAW:
11043             if (appData.debugMode)
11044               fprintf(debugFP, "Restoring %s for game %d\n",
11045                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11046
11047             thinkOutput[0] = NULLCHAR;
11048             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11049             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11050             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11051             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11052             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11053             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11054             MakeMove(fromX, fromY, toX, toY, promoChar);
11055             ShowMove(fromX, fromY, toX, toY);
11056
11057             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11058               case MT_NONE:
11059               case MT_CHECK:
11060                 break;
11061
11062               case MT_CHECKMATE:
11063               case MT_STAINMATE:
11064                 if (WhiteOnMove(currentMove)) {
11065                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11066                 } else {
11067                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11068                 }
11069                 break;
11070
11071               case MT_STALEMATE:
11072                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11073                 break;
11074             }
11075
11076             break;
11077
11078           case CMAIL_RESIGN:
11079             if (WhiteOnMove(currentMove)) {
11080                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11081             } else {
11082                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11083             }
11084             break;
11085
11086           case CMAIL_ACCEPT:
11087             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11088             break;
11089
11090           default:
11091             break;
11092         }
11093     }
11094
11095     return;
11096 }
11097
11098 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11099 int
11100 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11101 {
11102     int retVal;
11103
11104     if (gameNumber > nCmailGames) {
11105         DisplayError(_("No more games in this message"), 0);
11106         return FALSE;
11107     }
11108     if (f == lastLoadGameFP) {
11109         int offset = gameNumber - lastLoadGameNumber;
11110         if (offset == 0) {
11111             cmailMsg[0] = NULLCHAR;
11112             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11113                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11114                 nCmailMovesRegistered--;
11115             }
11116             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11117             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11118                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11119             }
11120         } else {
11121             if (! RegisterMove()) return FALSE;
11122         }
11123     }
11124
11125     retVal = LoadGame(f, gameNumber, title, useList);
11126
11127     /* Make move registered during previous look at this game, if any */
11128     MakeRegisteredMove();
11129
11130     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11131         commentList[currentMove]
11132           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11133         DisplayComment(currentMove - 1, commentList[currentMove]);
11134     }
11135
11136     return retVal;
11137 }
11138
11139 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11140 int
11141 ReloadGame (int offset)
11142 {
11143     int gameNumber = lastLoadGameNumber + offset;
11144     if (lastLoadGameFP == NULL) {
11145         DisplayError(_("No game has been loaded yet"), 0);
11146         return FALSE;
11147     }
11148     if (gameNumber <= 0) {
11149         DisplayError(_("Can't back up any further"), 0);
11150         return FALSE;
11151     }
11152     if (cmailMsgLoaded) {
11153         return CmailLoadGame(lastLoadGameFP, gameNumber,
11154                              lastLoadGameTitle, lastLoadGameUseList);
11155     } else {
11156         return LoadGame(lastLoadGameFP, gameNumber,
11157                         lastLoadGameTitle, lastLoadGameUseList);
11158     }
11159 }
11160
11161 int keys[EmptySquare+1];
11162
11163 int
11164 PositionMatches (Board b1, Board b2)
11165 {
11166     int r, f, sum=0;
11167     switch(appData.searchMode) {
11168         case 1: return CompareWithRights(b1, b2);
11169         case 2:
11170             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11171                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11172             }
11173             return TRUE;
11174         case 3:
11175             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11176               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11177                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11178             }
11179             return sum==0;
11180         case 4:
11181             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11182                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11183             }
11184             return sum==0;
11185     }
11186     return TRUE;
11187 }
11188
11189 #define Q_PROMO  4
11190 #define Q_EP     3
11191 #define Q_BCASTL 2
11192 #define Q_WCASTL 1
11193
11194 int pieceList[256], quickBoard[256];
11195 ChessSquare pieceType[256] = { EmptySquare };
11196 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11197 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11198 int soughtTotal, turn;
11199 Boolean epOK, flipSearch;
11200
11201 typedef struct {
11202     unsigned char piece, to;
11203 } Move;
11204
11205 #define DSIZE (250000)
11206
11207 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11208 Move *moveDatabase = initialSpace;
11209 unsigned int movePtr, dataSize = DSIZE;
11210
11211 int
11212 MakePieceList (Board board, int *counts)
11213 {
11214     int r, f, n=Q_PROMO, total=0;
11215     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11216     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11217         int sq = f + (r<<4);
11218         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11219             quickBoard[sq] = ++n;
11220             pieceList[n] = sq;
11221             pieceType[n] = board[r][f];
11222             counts[board[r][f]]++;
11223             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11224             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11225             total++;
11226         }
11227     }
11228     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11229     return total;
11230 }
11231
11232 void
11233 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11234 {
11235     int sq = fromX + (fromY<<4);
11236     int piece = quickBoard[sq];
11237     quickBoard[sq] = 0;
11238     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11239     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11240         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11241         moveDatabase[movePtr++].piece = Q_WCASTL;
11242         quickBoard[sq] = piece;
11243         piece = quickBoard[from]; quickBoard[from] = 0;
11244         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11245     } else
11246     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11247         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11248         moveDatabase[movePtr++].piece = Q_BCASTL;
11249         quickBoard[sq] = piece;
11250         piece = quickBoard[from]; quickBoard[from] = 0;
11251         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11252     } else
11253     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11254         quickBoard[(fromY<<4)+toX] = 0;
11255         moveDatabase[movePtr].piece = Q_EP;
11256         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11257         moveDatabase[movePtr].to = sq;
11258     } else
11259     if(promoPiece != pieceType[piece]) {
11260         moveDatabase[movePtr++].piece = Q_PROMO;
11261         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11262     }
11263     moveDatabase[movePtr].piece = piece;
11264     quickBoard[sq] = piece;
11265     movePtr++;
11266 }
11267
11268 int
11269 PackGame (Board board)
11270 {
11271     Move *newSpace = NULL;
11272     moveDatabase[movePtr].piece = 0; // terminate previous game
11273     if(movePtr > dataSize) {
11274         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11275         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11276         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11277         if(newSpace) {
11278             int i;
11279             Move *p = moveDatabase, *q = newSpace;
11280             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11281             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11282             moveDatabase = newSpace;
11283         } else { // calloc failed, we must be out of memory. Too bad...
11284             dataSize = 0; // prevent calloc events for all subsequent games
11285             return 0;     // and signal this one isn't cached
11286         }
11287     }
11288     movePtr++;
11289     MakePieceList(board, counts);
11290     return movePtr;
11291 }
11292
11293 int
11294 QuickCompare (Board board, int *minCounts, int *maxCounts)
11295 {   // compare according to search mode
11296     int r, f;
11297     switch(appData.searchMode)
11298     {
11299       case 1: // exact position match
11300         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11301         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11302             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11303         }
11304         break;
11305       case 2: // can have extra material on empty squares
11306         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11307             if(board[r][f] == EmptySquare) continue;
11308             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11309         }
11310         break;
11311       case 3: // material with exact Pawn structure
11312         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11313             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11314             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11315         } // fall through to material comparison
11316       case 4: // exact material
11317         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11318         break;
11319       case 6: // material range with given imbalance
11320         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11321         // fall through to range comparison
11322       case 5: // material range
11323         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11324     }
11325     return TRUE;
11326 }
11327
11328 int
11329 QuickScan (Board board, Move *move)
11330 {   // reconstruct game,and compare all positions in it
11331     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11332     do {
11333         int piece = move->piece;
11334         int to = move->to, from = pieceList[piece];
11335         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11336           if(!piece) return -1;
11337           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11338             piece = (++move)->piece;
11339             from = pieceList[piece];
11340             counts[pieceType[piece]]--;
11341             pieceType[piece] = (ChessSquare) move->to;
11342             counts[move->to]++;
11343           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11344             counts[pieceType[quickBoard[to]]]--;
11345             quickBoard[to] = 0; total--;
11346             move++;
11347             continue;
11348           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11349             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11350             from  = pieceList[piece]; // so this must be King
11351             quickBoard[from] = 0;
11352             quickBoard[to] = piece;
11353             pieceList[piece] = to;
11354             move++;
11355             continue;
11356           }
11357         }
11358         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11359         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11360         quickBoard[from] = 0;
11361         quickBoard[to] = piece;
11362         pieceList[piece] = to;
11363         cnt++; turn ^= 3;
11364         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11365            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11366            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11367                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11368           ) {
11369             static int lastCounts[EmptySquare+1];
11370             int i;
11371             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11372             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11373         } else stretch = 0;
11374         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11375         move++;
11376     } while(1);
11377 }
11378
11379 void
11380 InitSearch ()
11381 {
11382     int r, f;
11383     flipSearch = FALSE;
11384     CopyBoard(soughtBoard, boards[currentMove]);
11385     soughtTotal = MakePieceList(soughtBoard, maxSought);
11386     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11387     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11388     CopyBoard(reverseBoard, boards[currentMove]);
11389     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11390         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11391         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11392         reverseBoard[r][f] = piece;
11393     }
11394     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11395     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11396     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11397                  || (boards[currentMove][CASTLING][2] == NoRights || 
11398                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11399                  && (boards[currentMove][CASTLING][5] == NoRights || 
11400                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11401       ) {
11402         flipSearch = TRUE;
11403         CopyBoard(flipBoard, soughtBoard);
11404         CopyBoard(rotateBoard, reverseBoard);
11405         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11406             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11407             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11408         }
11409     }
11410     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11411     if(appData.searchMode >= 5) {
11412         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11413         MakePieceList(soughtBoard, minSought);
11414         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11415     }
11416     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11417         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11418 }
11419
11420 GameInfo dummyInfo;
11421
11422 int
11423 GameContainsPosition (FILE *f, ListGame *lg)
11424 {
11425     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11426     int fromX, fromY, toX, toY;
11427     char promoChar;
11428     static int initDone=FALSE;
11429
11430     // weed out games based on numerical tag comparison
11431     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11432     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11433     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11434     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11435     if(!initDone) {
11436         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11437         initDone = TRUE;
11438     }
11439     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11440     else CopyBoard(boards[scratch], initialPosition); // default start position
11441     if(lg->moves) {
11442         turn = btm + 1;
11443         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11444         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11445     }
11446     if(btm) plyNr++;
11447     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11448     fseek(f, lg->offset, 0);
11449     yynewfile(f);
11450     while(1) {
11451         yyboardindex = scratch;
11452         quickFlag = plyNr+1;
11453         next = Myylex();
11454         quickFlag = 0;
11455         switch(next) {
11456             case PGNTag:
11457                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11458             default:
11459                 continue;
11460
11461             case XBoardGame:
11462             case GNUChessGame:
11463                 if(plyNr) return -1; // after we have seen moves, this is for new game
11464               continue;
11465
11466             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11467             case ImpossibleMove:
11468             case WhiteWins: // game ends here with these four
11469             case BlackWins:
11470             case GameIsDrawn:
11471             case GameUnfinished:
11472                 return -1;
11473
11474             case IllegalMove:
11475                 if(appData.testLegality) return -1;
11476             case WhiteCapturesEnPassant:
11477             case BlackCapturesEnPassant:
11478             case WhitePromotion:
11479             case BlackPromotion:
11480             case WhiteNonPromotion:
11481             case BlackNonPromotion:
11482             case NormalMove:
11483             case WhiteKingSideCastle:
11484             case WhiteQueenSideCastle:
11485             case BlackKingSideCastle:
11486             case BlackQueenSideCastle:
11487             case WhiteKingSideCastleWild:
11488             case WhiteQueenSideCastleWild:
11489             case BlackKingSideCastleWild:
11490             case BlackQueenSideCastleWild:
11491             case WhiteHSideCastleFR:
11492             case WhiteASideCastleFR:
11493             case BlackHSideCastleFR:
11494             case BlackASideCastleFR:
11495                 fromX = currentMoveString[0] - AAA;
11496                 fromY = currentMoveString[1] - ONE;
11497                 toX = currentMoveString[2] - AAA;
11498                 toY = currentMoveString[3] - ONE;
11499                 promoChar = currentMoveString[4];
11500                 break;
11501             case WhiteDrop:
11502             case BlackDrop:
11503                 fromX = next == WhiteDrop ?
11504                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11505                   (int) CharToPiece(ToLower(currentMoveString[0]));
11506                 fromY = DROP_RANK;
11507                 toX = currentMoveString[2] - AAA;
11508                 toY = currentMoveString[3] - ONE;
11509                 promoChar = 0;
11510                 break;
11511         }
11512         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11513         plyNr++;
11514         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11515         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11516         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11517         if(appData.findMirror) {
11518             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11519             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11520         }
11521     }
11522 }
11523
11524 /* Load the nth game from open file f */
11525 int
11526 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11527 {
11528     ChessMove cm;
11529     char buf[MSG_SIZ];
11530     int gn = gameNumber;
11531     ListGame *lg = NULL;
11532     int numPGNTags = 0;
11533     int err, pos = -1;
11534     GameMode oldGameMode;
11535     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11536
11537     if (appData.debugMode)
11538         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11539
11540     if (gameMode == Training )
11541         SetTrainingModeOff();
11542
11543     oldGameMode = gameMode;
11544     if (gameMode != BeginningOfGame) {
11545       Reset(FALSE, TRUE);
11546     }
11547
11548     gameFileFP = f;
11549     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11550         fclose(lastLoadGameFP);
11551     }
11552
11553     if (useList) {
11554         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11555
11556         if (lg) {
11557             fseek(f, lg->offset, 0);
11558             GameListHighlight(gameNumber);
11559             pos = lg->position;
11560             gn = 1;
11561         }
11562         else {
11563             DisplayError(_("Game number out of range"), 0);
11564             return FALSE;
11565         }
11566     } else {
11567         GameListDestroy();
11568         if (fseek(f, 0, 0) == -1) {
11569             if (f == lastLoadGameFP ?
11570                 gameNumber == lastLoadGameNumber + 1 :
11571                 gameNumber == 1) {
11572                 gn = 1;
11573             } else {
11574                 DisplayError(_("Can't seek on game file"), 0);
11575                 return FALSE;
11576             }
11577         }
11578     }
11579     lastLoadGameFP = f;
11580     lastLoadGameNumber = gameNumber;
11581     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11582     lastLoadGameUseList = useList;
11583
11584     yynewfile(f);
11585
11586     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11587       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11588                 lg->gameInfo.black);
11589             DisplayTitle(buf);
11590     } else if (*title != NULLCHAR) {
11591         if (gameNumber > 1) {
11592           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11593             DisplayTitle(buf);
11594         } else {
11595             DisplayTitle(title);
11596         }
11597     }
11598
11599     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11600         gameMode = PlayFromGameFile;
11601         ModeHighlight();
11602     }
11603
11604     currentMove = forwardMostMove = backwardMostMove = 0;
11605     CopyBoard(boards[0], initialPosition);
11606     StopClocks();
11607
11608     /*
11609      * Skip the first gn-1 games in the file.
11610      * Also skip over anything that precedes an identifiable
11611      * start of game marker, to avoid being confused by
11612      * garbage at the start of the file.  Currently
11613      * recognized start of game markers are the move number "1",
11614      * the pattern "gnuchess .* game", the pattern
11615      * "^[#;%] [^ ]* game file", and a PGN tag block.
11616      * A game that starts with one of the latter two patterns
11617      * will also have a move number 1, possibly
11618      * following a position diagram.
11619      * 5-4-02: Let's try being more lenient and allowing a game to
11620      * start with an unnumbered move.  Does that break anything?
11621      */
11622     cm = lastLoadGameStart = EndOfFile;
11623     while (gn > 0) {
11624         yyboardindex = forwardMostMove;
11625         cm = (ChessMove) Myylex();
11626         switch (cm) {
11627           case EndOfFile:
11628             if (cmailMsgLoaded) {
11629                 nCmailGames = CMAIL_MAX_GAMES - gn;
11630             } else {
11631                 Reset(TRUE, TRUE);
11632                 DisplayError(_("Game not found in file"), 0);
11633             }
11634             return FALSE;
11635
11636           case GNUChessGame:
11637           case XBoardGame:
11638             gn--;
11639             lastLoadGameStart = cm;
11640             break;
11641
11642           case MoveNumberOne:
11643             switch (lastLoadGameStart) {
11644               case GNUChessGame:
11645               case XBoardGame:
11646               case PGNTag:
11647                 break;
11648               case MoveNumberOne:
11649               case EndOfFile:
11650                 gn--;           /* count this game */
11651                 lastLoadGameStart = cm;
11652                 break;
11653               default:
11654                 /* impossible */
11655                 break;
11656             }
11657             break;
11658
11659           case PGNTag:
11660             switch (lastLoadGameStart) {
11661               case GNUChessGame:
11662               case PGNTag:
11663               case MoveNumberOne:
11664               case EndOfFile:
11665                 gn--;           /* count this game */
11666                 lastLoadGameStart = cm;
11667                 break;
11668               case XBoardGame:
11669                 lastLoadGameStart = cm; /* game counted already */
11670                 break;
11671               default:
11672                 /* impossible */
11673                 break;
11674             }
11675             if (gn > 0) {
11676                 do {
11677                     yyboardindex = forwardMostMove;
11678                     cm = (ChessMove) Myylex();
11679                 } while (cm == PGNTag || cm == Comment);
11680             }
11681             break;
11682
11683           case WhiteWins:
11684           case BlackWins:
11685           case GameIsDrawn:
11686             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11687                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11688                     != CMAIL_OLD_RESULT) {
11689                     nCmailResults ++ ;
11690                     cmailResult[  CMAIL_MAX_GAMES
11691                                 - gn - 1] = CMAIL_OLD_RESULT;
11692                 }
11693             }
11694             break;
11695
11696           case NormalMove:
11697             /* Only a NormalMove can be at the start of a game
11698              * without a position diagram. */
11699             if (lastLoadGameStart == EndOfFile ) {
11700               gn--;
11701               lastLoadGameStart = MoveNumberOne;
11702             }
11703             break;
11704
11705           default:
11706             break;
11707         }
11708     }
11709
11710     if (appData.debugMode)
11711       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11712
11713     if (cm == XBoardGame) {
11714         /* Skip any header junk before position diagram and/or move 1 */
11715         for (;;) {
11716             yyboardindex = forwardMostMove;
11717             cm = (ChessMove) Myylex();
11718
11719             if (cm == EndOfFile ||
11720                 cm == GNUChessGame || cm == XBoardGame) {
11721                 /* Empty game; pretend end-of-file and handle later */
11722                 cm = EndOfFile;
11723                 break;
11724             }
11725
11726             if (cm == MoveNumberOne || cm == PositionDiagram ||
11727                 cm == PGNTag || cm == Comment)
11728               break;
11729         }
11730     } else if (cm == GNUChessGame) {
11731         if (gameInfo.event != NULL) {
11732             free(gameInfo.event);
11733         }
11734         gameInfo.event = StrSave(yy_text);
11735     }
11736
11737     startedFromSetupPosition = FALSE;
11738     while (cm == PGNTag) {
11739         if (appData.debugMode)
11740           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11741         err = ParsePGNTag(yy_text, &gameInfo);
11742         if (!err) numPGNTags++;
11743
11744         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11745         if(gameInfo.variant != oldVariant) {
11746             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11747             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11748             InitPosition(TRUE);
11749             oldVariant = gameInfo.variant;
11750             if (appData.debugMode)
11751               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11752         }
11753
11754
11755         if (gameInfo.fen != NULL) {
11756           Board initial_position;
11757           startedFromSetupPosition = TRUE;
11758           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11759             Reset(TRUE, TRUE);
11760             DisplayError(_("Bad FEN position in file"), 0);
11761             return FALSE;
11762           }
11763           CopyBoard(boards[0], initial_position);
11764           if (blackPlaysFirst) {
11765             currentMove = forwardMostMove = backwardMostMove = 1;
11766             CopyBoard(boards[1], initial_position);
11767             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11768             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11769             timeRemaining[0][1] = whiteTimeRemaining;
11770             timeRemaining[1][1] = blackTimeRemaining;
11771             if (commentList[0] != NULL) {
11772               commentList[1] = commentList[0];
11773               commentList[0] = NULL;
11774             }
11775           } else {
11776             currentMove = forwardMostMove = backwardMostMove = 0;
11777           }
11778           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11779           {   int i;
11780               initialRulePlies = FENrulePlies;
11781               for( i=0; i< nrCastlingRights; i++ )
11782                   initialRights[i] = initial_position[CASTLING][i];
11783           }
11784           yyboardindex = forwardMostMove;
11785           free(gameInfo.fen);
11786           gameInfo.fen = NULL;
11787         }
11788
11789         yyboardindex = forwardMostMove;
11790         cm = (ChessMove) Myylex();
11791
11792         /* Handle comments interspersed among the tags */
11793         while (cm == Comment) {
11794             char *p;
11795             if (appData.debugMode)
11796               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11797             p = yy_text;
11798             AppendComment(currentMove, p, FALSE);
11799             yyboardindex = forwardMostMove;
11800             cm = (ChessMove) Myylex();
11801         }
11802     }
11803
11804     /* don't rely on existence of Event tag since if game was
11805      * pasted from clipboard the Event tag may not exist
11806      */
11807     if (numPGNTags > 0){
11808         char *tags;
11809         if (gameInfo.variant == VariantNormal) {
11810           VariantClass v = StringToVariant(gameInfo.event);
11811           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11812           if(v < VariantShogi) gameInfo.variant = v;
11813         }
11814         if (!matchMode) {
11815           if( appData.autoDisplayTags ) {
11816             tags = PGNTags(&gameInfo);
11817             TagsPopUp(tags, CmailMsg());
11818             free(tags);
11819           }
11820         }
11821     } else {
11822         /* Make something up, but don't display it now */
11823         SetGameInfo();
11824         TagsPopDown();
11825     }
11826
11827     if (cm == PositionDiagram) {
11828         int i, j;
11829         char *p;
11830         Board initial_position;
11831
11832         if (appData.debugMode)
11833           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11834
11835         if (!startedFromSetupPosition) {
11836             p = yy_text;
11837             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11838               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11839                 switch (*p) {
11840                   case '{':
11841                   case '[':
11842                   case '-':
11843                   case ' ':
11844                   case '\t':
11845                   case '\n':
11846                   case '\r':
11847                     break;
11848                   default:
11849                     initial_position[i][j++] = CharToPiece(*p);
11850                     break;
11851                 }
11852             while (*p == ' ' || *p == '\t' ||
11853                    *p == '\n' || *p == '\r') p++;
11854
11855             if (strncmp(p, "black", strlen("black"))==0)
11856               blackPlaysFirst = TRUE;
11857             else
11858               blackPlaysFirst = FALSE;
11859             startedFromSetupPosition = TRUE;
11860
11861             CopyBoard(boards[0], initial_position);
11862             if (blackPlaysFirst) {
11863                 currentMove = forwardMostMove = backwardMostMove = 1;
11864                 CopyBoard(boards[1], initial_position);
11865                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11866                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11867                 timeRemaining[0][1] = whiteTimeRemaining;
11868                 timeRemaining[1][1] = blackTimeRemaining;
11869                 if (commentList[0] != NULL) {
11870                     commentList[1] = commentList[0];
11871                     commentList[0] = NULL;
11872                 }
11873             } else {
11874                 currentMove = forwardMostMove = backwardMostMove = 0;
11875             }
11876         }
11877         yyboardindex = forwardMostMove;
11878         cm = (ChessMove) Myylex();
11879     }
11880
11881     if (first.pr == NoProc) {
11882         StartChessProgram(&first);
11883     }
11884     InitChessProgram(&first, FALSE);
11885     SendToProgram("force\n", &first);
11886     if (startedFromSetupPosition) {
11887         SendBoard(&first, forwardMostMove);
11888     if (appData.debugMode) {
11889         fprintf(debugFP, "Load Game\n");
11890     }
11891         DisplayBothClocks();
11892     }
11893
11894     /* [HGM] server: flag to write setup moves in broadcast file as one */
11895     loadFlag = appData.suppressLoadMoves;
11896
11897     while (cm == Comment) {
11898         char *p;
11899         if (appData.debugMode)
11900           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11901         p = yy_text;
11902         AppendComment(currentMove, p, FALSE);
11903         yyboardindex = forwardMostMove;
11904         cm = (ChessMove) Myylex();
11905     }
11906
11907     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11908         cm == WhiteWins || cm == BlackWins ||
11909         cm == GameIsDrawn || cm == GameUnfinished) {
11910         DisplayMessage("", _("No moves in game"));
11911         if (cmailMsgLoaded) {
11912             if (appData.debugMode)
11913               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11914             ClearHighlights();
11915             flipView = FALSE;
11916         }
11917         DrawPosition(FALSE, boards[currentMove]);
11918         DisplayBothClocks();
11919         gameMode = EditGame;
11920         ModeHighlight();
11921         gameFileFP = NULL;
11922         cmailOldMove = 0;
11923         return TRUE;
11924     }
11925
11926     // [HGM] PV info: routine tests if comment empty
11927     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11928         DisplayComment(currentMove - 1, commentList[currentMove]);
11929     }
11930     if (!matchMode && appData.timeDelay != 0)
11931       DrawPosition(FALSE, boards[currentMove]);
11932
11933     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11934       programStats.ok_to_send = 1;
11935     }
11936
11937     /* if the first token after the PGN tags is a move
11938      * and not move number 1, retrieve it from the parser
11939      */
11940     if (cm != MoveNumberOne)
11941         LoadGameOneMove(cm);
11942
11943     /* load the remaining moves from the file */
11944     while (LoadGameOneMove(EndOfFile)) {
11945       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11946       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11947     }
11948
11949     /* rewind to the start of the game */
11950     currentMove = backwardMostMove;
11951
11952     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11953
11954     if (oldGameMode == AnalyzeFile ||
11955         oldGameMode == AnalyzeMode) {
11956       AnalyzeFileEvent();
11957     }
11958
11959     if (!matchMode && pos >= 0) {
11960         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11961     } else
11962     if (matchMode || appData.timeDelay == 0) {
11963       ToEndEvent();
11964     } else if (appData.timeDelay > 0) {
11965       AutoPlayGameLoop();
11966     }
11967
11968     if (appData.debugMode)
11969         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11970
11971     loadFlag = 0; /* [HGM] true game starts */
11972     return TRUE;
11973 }
11974
11975 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11976 int
11977 ReloadPosition (int offset)
11978 {
11979     int positionNumber = lastLoadPositionNumber + offset;
11980     if (lastLoadPositionFP == NULL) {
11981         DisplayError(_("No position has been loaded yet"), 0);
11982         return FALSE;
11983     }
11984     if (positionNumber <= 0) {
11985         DisplayError(_("Can't back up any further"), 0);
11986         return FALSE;
11987     }
11988     return LoadPosition(lastLoadPositionFP, positionNumber,
11989                         lastLoadPositionTitle);
11990 }
11991
11992 /* Load the nth position from the given file */
11993 int
11994 LoadPositionFromFile (char *filename, int n, char *title)
11995 {
11996     FILE *f;
11997     char buf[MSG_SIZ];
11998
11999     if (strcmp(filename, "-") == 0) {
12000         return LoadPosition(stdin, n, "stdin");
12001     } else {
12002         f = fopen(filename, "rb");
12003         if (f == NULL) {
12004             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12005             DisplayError(buf, errno);
12006             return FALSE;
12007         } else {
12008             return LoadPosition(f, n, title);
12009         }
12010     }
12011 }
12012
12013 /* Load the nth position from the given open file, and close it */
12014 int
12015 LoadPosition (FILE *f, int positionNumber, char *title)
12016 {
12017     char *p, line[MSG_SIZ];
12018     Board initial_position;
12019     int i, j, fenMode, pn;
12020
12021     if (gameMode == Training )
12022         SetTrainingModeOff();
12023
12024     if (gameMode != BeginningOfGame) {
12025         Reset(FALSE, TRUE);
12026     }
12027     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12028         fclose(lastLoadPositionFP);
12029     }
12030     if (positionNumber == 0) positionNumber = 1;
12031     lastLoadPositionFP = f;
12032     lastLoadPositionNumber = positionNumber;
12033     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12034     if (first.pr == NoProc && !appData.noChessProgram) {
12035       StartChessProgram(&first);
12036       InitChessProgram(&first, FALSE);
12037     }
12038     pn = positionNumber;
12039     if (positionNumber < 0) {
12040         /* Negative position number means to seek to that byte offset */
12041         if (fseek(f, -positionNumber, 0) == -1) {
12042             DisplayError(_("Can't seek on position file"), 0);
12043             return FALSE;
12044         };
12045         pn = 1;
12046     } else {
12047         if (fseek(f, 0, 0) == -1) {
12048             if (f == lastLoadPositionFP ?
12049                 positionNumber == lastLoadPositionNumber + 1 :
12050                 positionNumber == 1) {
12051                 pn = 1;
12052             } else {
12053                 DisplayError(_("Can't seek on position file"), 0);
12054                 return FALSE;
12055             }
12056         }
12057     }
12058     /* See if this file is FEN or old-style xboard */
12059     if (fgets(line, MSG_SIZ, f) == NULL) {
12060         DisplayError(_("Position not found in file"), 0);
12061         return FALSE;
12062     }
12063     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12064     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12065
12066     if (pn >= 2) {
12067         if (fenMode || line[0] == '#') pn--;
12068         while (pn > 0) {
12069             /* skip positions before number pn */
12070             if (fgets(line, MSG_SIZ, f) == NULL) {
12071                 Reset(TRUE, TRUE);
12072                 DisplayError(_("Position not found in file"), 0);
12073                 return FALSE;
12074             }
12075             if (fenMode || line[0] == '#') pn--;
12076         }
12077     }
12078
12079     if (fenMode) {
12080         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12081             DisplayError(_("Bad FEN position in file"), 0);
12082             return FALSE;
12083         }
12084     } else {
12085         (void) fgets(line, MSG_SIZ, f);
12086         (void) fgets(line, MSG_SIZ, f);
12087
12088         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12089             (void) fgets(line, MSG_SIZ, f);
12090             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12091                 if (*p == ' ')
12092                   continue;
12093                 initial_position[i][j++] = CharToPiece(*p);
12094             }
12095         }
12096
12097         blackPlaysFirst = FALSE;
12098         if (!feof(f)) {
12099             (void) fgets(line, MSG_SIZ, f);
12100             if (strncmp(line, "black", strlen("black"))==0)
12101               blackPlaysFirst = TRUE;
12102         }
12103     }
12104     startedFromSetupPosition = TRUE;
12105
12106     CopyBoard(boards[0], initial_position);
12107     if (blackPlaysFirst) {
12108         currentMove = forwardMostMove = backwardMostMove = 1;
12109         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12110         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12111         CopyBoard(boards[1], initial_position);
12112         DisplayMessage("", _("Black to play"));
12113     } else {
12114         currentMove = forwardMostMove = backwardMostMove = 0;
12115         DisplayMessage("", _("White to play"));
12116     }
12117     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12118     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12119         SendToProgram("force\n", &first);
12120         SendBoard(&first, forwardMostMove);
12121     }
12122     if (appData.debugMode) {
12123 int i, j;
12124   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12125   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12126         fprintf(debugFP, "Load Position\n");
12127     }
12128
12129     if (positionNumber > 1) {
12130       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12131         DisplayTitle(line);
12132     } else {
12133         DisplayTitle(title);
12134     }
12135     gameMode = EditGame;
12136     ModeHighlight();
12137     ResetClocks();
12138     timeRemaining[0][1] = whiteTimeRemaining;
12139     timeRemaining[1][1] = blackTimeRemaining;
12140     DrawPosition(FALSE, boards[currentMove]);
12141
12142     return TRUE;
12143 }
12144
12145
12146 void
12147 CopyPlayerNameIntoFileName (char **dest, char *src)
12148 {
12149     while (*src != NULLCHAR && *src != ',') {
12150         if (*src == ' ') {
12151             *(*dest)++ = '_';
12152             src++;
12153         } else {
12154             *(*dest)++ = *src++;
12155         }
12156     }
12157 }
12158
12159 char *
12160 DefaultFileName (char *ext)
12161 {
12162     static char def[MSG_SIZ];
12163     char *p;
12164
12165     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12166         p = def;
12167         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12168         *p++ = '-';
12169         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12170         *p++ = '.';
12171         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12172     } else {
12173         def[0] = NULLCHAR;
12174     }
12175     return def;
12176 }
12177
12178 /* Save the current game to the given file */
12179 int
12180 SaveGameToFile (char *filename, int append)
12181 {
12182     FILE *f;
12183     char buf[MSG_SIZ];
12184     int result, i, t,tot=0;
12185
12186     if (strcmp(filename, "-") == 0) {
12187         return SaveGame(stdout, 0, NULL);
12188     } else {
12189         for(i=0; i<10; i++) { // upto 10 tries
12190              f = fopen(filename, append ? "a" : "w");
12191              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12192              if(f || errno != 13) break;
12193              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12194              tot += t;
12195         }
12196         if (f == NULL) {
12197             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12198             DisplayError(buf, errno);
12199             return FALSE;
12200         } else {
12201             safeStrCpy(buf, lastMsg, MSG_SIZ);
12202             DisplayMessage(_("Waiting for access to save file"), "");
12203             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12204             DisplayMessage(_("Saving game"), "");
12205             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12206             result = SaveGame(f, 0, NULL);
12207             DisplayMessage(buf, "");
12208             return result;
12209         }
12210     }
12211 }
12212
12213 char *
12214 SavePart (char *str)
12215 {
12216     static char buf[MSG_SIZ];
12217     char *p;
12218
12219     p = strchr(str, ' ');
12220     if (p == NULL) return str;
12221     strncpy(buf, str, p - str);
12222     buf[p - str] = NULLCHAR;
12223     return buf;
12224 }
12225
12226 #define PGN_MAX_LINE 75
12227
12228 #define PGN_SIDE_WHITE  0
12229 #define PGN_SIDE_BLACK  1
12230
12231 static int
12232 FindFirstMoveOutOfBook (int side)
12233 {
12234     int result = -1;
12235
12236     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12237         int index = backwardMostMove;
12238         int has_book_hit = 0;
12239
12240         if( (index % 2) != side ) {
12241             index++;
12242         }
12243
12244         while( index < forwardMostMove ) {
12245             /* Check to see if engine is in book */
12246             int depth = pvInfoList[index].depth;
12247             int score = pvInfoList[index].score;
12248             int in_book = 0;
12249
12250             if( depth <= 2 ) {
12251                 in_book = 1;
12252             }
12253             else if( score == 0 && depth == 63 ) {
12254                 in_book = 1; /* Zappa */
12255             }
12256             else if( score == 2 && depth == 99 ) {
12257                 in_book = 1; /* Abrok */
12258             }
12259
12260             has_book_hit += in_book;
12261
12262             if( ! in_book ) {
12263                 result = index;
12264
12265                 break;
12266             }
12267
12268             index += 2;
12269         }
12270     }
12271
12272     return result;
12273 }
12274
12275 void
12276 GetOutOfBookInfo (char * buf)
12277 {
12278     int oob[2];
12279     int i;
12280     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12281
12282     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12283     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12284
12285     *buf = '\0';
12286
12287     if( oob[0] >= 0 || oob[1] >= 0 ) {
12288         for( i=0; i<2; i++ ) {
12289             int idx = oob[i];
12290
12291             if( idx >= 0 ) {
12292                 if( i > 0 && oob[0] >= 0 ) {
12293                     strcat( buf, "   " );
12294                 }
12295
12296                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12297                 sprintf( buf+strlen(buf), "%s%.2f",
12298                     pvInfoList[idx].score >= 0 ? "+" : "",
12299                     pvInfoList[idx].score / 100.0 );
12300             }
12301         }
12302     }
12303 }
12304
12305 /* Save game in PGN style and close the file */
12306 int
12307 SaveGamePGN (FILE *f)
12308 {
12309     int i, offset, linelen, newblock;
12310     time_t tm;
12311 //    char *movetext;
12312     char numtext[32];
12313     int movelen, numlen, blank;
12314     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12315
12316     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12317
12318     tm = time((time_t *) NULL);
12319
12320     PrintPGNTags(f, &gameInfo);
12321
12322     if (backwardMostMove > 0 || startedFromSetupPosition) {
12323         char *fen = PositionToFEN(backwardMostMove, NULL);
12324         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12325         fprintf(f, "\n{--------------\n");
12326         PrintPosition(f, backwardMostMove);
12327         fprintf(f, "--------------}\n");
12328         free(fen);
12329     }
12330     else {
12331         /* [AS] Out of book annotation */
12332         if( appData.saveOutOfBookInfo ) {
12333             char buf[64];
12334
12335             GetOutOfBookInfo( buf );
12336
12337             if( buf[0] != '\0' ) {
12338                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12339             }
12340         }
12341
12342         fprintf(f, "\n");
12343     }
12344
12345     i = backwardMostMove;
12346     linelen = 0;
12347     newblock = TRUE;
12348
12349     while (i < forwardMostMove) {
12350         /* Print comments preceding this move */
12351         if (commentList[i] != NULL) {
12352             if (linelen > 0) fprintf(f, "\n");
12353             fprintf(f, "%s", commentList[i]);
12354             linelen = 0;
12355             newblock = TRUE;
12356         }
12357
12358         /* Format move number */
12359         if ((i % 2) == 0)
12360           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12361         else
12362           if (newblock)
12363             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12364           else
12365             numtext[0] = NULLCHAR;
12366
12367         numlen = strlen(numtext);
12368         newblock = FALSE;
12369
12370         /* Print move number */
12371         blank = linelen > 0 && numlen > 0;
12372         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12373             fprintf(f, "\n");
12374             linelen = 0;
12375             blank = 0;
12376         }
12377         if (blank) {
12378             fprintf(f, " ");
12379             linelen++;
12380         }
12381         fprintf(f, "%s", numtext);
12382         linelen += numlen;
12383
12384         /* Get move */
12385         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12386         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12387
12388         /* Print move */
12389         blank = linelen > 0 && movelen > 0;
12390         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12391             fprintf(f, "\n");
12392             linelen = 0;
12393             blank = 0;
12394         }
12395         if (blank) {
12396             fprintf(f, " ");
12397             linelen++;
12398         }
12399         fprintf(f, "%s", move_buffer);
12400         linelen += movelen;
12401
12402         /* [AS] Add PV info if present */
12403         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12404             /* [HGM] add time */
12405             char buf[MSG_SIZ]; int seconds;
12406
12407             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12408
12409             if( seconds <= 0)
12410               buf[0] = 0;
12411             else
12412               if( seconds < 30 )
12413                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12414               else
12415                 {
12416                   seconds = (seconds + 4)/10; // round to full seconds
12417                   if( seconds < 60 )
12418                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12419                   else
12420                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12421                 }
12422
12423             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12424                       pvInfoList[i].score >= 0 ? "+" : "",
12425                       pvInfoList[i].score / 100.0,
12426                       pvInfoList[i].depth,
12427                       buf );
12428
12429             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12430
12431             /* Print score/depth */
12432             blank = linelen > 0 && movelen > 0;
12433             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12434                 fprintf(f, "\n");
12435                 linelen = 0;
12436                 blank = 0;
12437             }
12438             if (blank) {
12439                 fprintf(f, " ");
12440                 linelen++;
12441             }
12442             fprintf(f, "%s", move_buffer);
12443             linelen += movelen;
12444         }
12445
12446         i++;
12447     }
12448
12449     /* Start a new line */
12450     if (linelen > 0) fprintf(f, "\n");
12451
12452     /* Print comments after last move */
12453     if (commentList[i] != NULL) {
12454         fprintf(f, "%s\n", commentList[i]);
12455     }
12456
12457     /* Print result */
12458     if (gameInfo.resultDetails != NULL &&
12459         gameInfo.resultDetails[0] != NULLCHAR) {
12460         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12461                 PGNResult(gameInfo.result));
12462     } else {
12463         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12464     }
12465
12466     fclose(f);
12467     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12468     return TRUE;
12469 }
12470
12471 /* Save game in old style and close the file */
12472 int
12473 SaveGameOldStyle (FILE *f)
12474 {
12475     int i, offset;
12476     time_t tm;
12477
12478     tm = time((time_t *) NULL);
12479
12480     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12481     PrintOpponents(f);
12482
12483     if (backwardMostMove > 0 || startedFromSetupPosition) {
12484         fprintf(f, "\n[--------------\n");
12485         PrintPosition(f, backwardMostMove);
12486         fprintf(f, "--------------]\n");
12487     } else {
12488         fprintf(f, "\n");
12489     }
12490
12491     i = backwardMostMove;
12492     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12493
12494     while (i < forwardMostMove) {
12495         if (commentList[i] != NULL) {
12496             fprintf(f, "[%s]\n", commentList[i]);
12497         }
12498
12499         if ((i % 2) == 1) {
12500             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12501             i++;
12502         } else {
12503             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12504             i++;
12505             if (commentList[i] != NULL) {
12506                 fprintf(f, "\n");
12507                 continue;
12508             }
12509             if (i >= forwardMostMove) {
12510                 fprintf(f, "\n");
12511                 break;
12512             }
12513             fprintf(f, "%s\n", parseList[i]);
12514             i++;
12515         }
12516     }
12517
12518     if (commentList[i] != NULL) {
12519         fprintf(f, "[%s]\n", commentList[i]);
12520     }
12521
12522     /* This isn't really the old style, but it's close enough */
12523     if (gameInfo.resultDetails != NULL &&
12524         gameInfo.resultDetails[0] != NULLCHAR) {
12525         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12526                 gameInfo.resultDetails);
12527     } else {
12528         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12529     }
12530
12531     fclose(f);
12532     return TRUE;
12533 }
12534
12535 /* Save the current game to open file f and close the file */
12536 int
12537 SaveGame (FILE *f, int dummy, char *dummy2)
12538 {
12539     if (gameMode == EditPosition) EditPositionDone(TRUE);
12540     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12541     if (appData.oldSaveStyle)
12542       return SaveGameOldStyle(f);
12543     else
12544       return SaveGamePGN(f);
12545 }
12546
12547 /* Save the current position to the given file */
12548 int
12549 SavePositionToFile (char *filename)
12550 {
12551     FILE *f;
12552     char buf[MSG_SIZ];
12553
12554     if (strcmp(filename, "-") == 0) {
12555         return SavePosition(stdout, 0, NULL);
12556     } else {
12557         f = fopen(filename, "a");
12558         if (f == NULL) {
12559             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12560             DisplayError(buf, errno);
12561             return FALSE;
12562         } else {
12563             safeStrCpy(buf, lastMsg, MSG_SIZ);
12564             DisplayMessage(_("Waiting for access to save file"), "");
12565             flock(fileno(f), LOCK_EX); // [HGM] lock
12566             DisplayMessage(_("Saving position"), "");
12567             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12568             SavePosition(f, 0, NULL);
12569             DisplayMessage(buf, "");
12570             return TRUE;
12571         }
12572     }
12573 }
12574
12575 /* Save the current position to the given open file and close the file */
12576 int
12577 SavePosition (FILE *f, int dummy, char *dummy2)
12578 {
12579     time_t tm;
12580     char *fen;
12581
12582     if (gameMode == EditPosition) EditPositionDone(TRUE);
12583     if (appData.oldSaveStyle) {
12584         tm = time((time_t *) NULL);
12585
12586         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12587         PrintOpponents(f);
12588         fprintf(f, "[--------------\n");
12589         PrintPosition(f, currentMove);
12590         fprintf(f, "--------------]\n");
12591     } else {
12592         fen = PositionToFEN(currentMove, NULL);
12593         fprintf(f, "%s\n", fen);
12594         free(fen);
12595     }
12596     fclose(f);
12597     return TRUE;
12598 }
12599
12600 void
12601 ReloadCmailMsgEvent (int unregister)
12602 {
12603 #if !WIN32
12604     static char *inFilename = NULL;
12605     static char *outFilename;
12606     int i;
12607     struct stat inbuf, outbuf;
12608     int status;
12609
12610     /* Any registered moves are unregistered if unregister is set, */
12611     /* i.e. invoked by the signal handler */
12612     if (unregister) {
12613         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12614             cmailMoveRegistered[i] = FALSE;
12615             if (cmailCommentList[i] != NULL) {
12616                 free(cmailCommentList[i]);
12617                 cmailCommentList[i] = NULL;
12618             }
12619         }
12620         nCmailMovesRegistered = 0;
12621     }
12622
12623     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12624         cmailResult[i] = CMAIL_NOT_RESULT;
12625     }
12626     nCmailResults = 0;
12627
12628     if (inFilename == NULL) {
12629         /* Because the filenames are static they only get malloced once  */
12630         /* and they never get freed                                      */
12631         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12632         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12633
12634         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12635         sprintf(outFilename, "%s.out", appData.cmailGameName);
12636     }
12637
12638     status = stat(outFilename, &outbuf);
12639     if (status < 0) {
12640         cmailMailedMove = FALSE;
12641     } else {
12642         status = stat(inFilename, &inbuf);
12643         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12644     }
12645
12646     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12647        counts the games, notes how each one terminated, etc.
12648
12649        It would be nice to remove this kludge and instead gather all
12650        the information while building the game list.  (And to keep it
12651        in the game list nodes instead of having a bunch of fixed-size
12652        parallel arrays.)  Note this will require getting each game's
12653        termination from the PGN tags, as the game list builder does
12654        not process the game moves.  --mann
12655        */
12656     cmailMsgLoaded = TRUE;
12657     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12658
12659     /* Load first game in the file or popup game menu */
12660     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12661
12662 #endif /* !WIN32 */
12663     return;
12664 }
12665
12666 int
12667 RegisterMove ()
12668 {
12669     FILE *f;
12670     char string[MSG_SIZ];
12671
12672     if (   cmailMailedMove
12673         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12674         return TRUE;            /* Allow free viewing  */
12675     }
12676
12677     /* Unregister move to ensure that we don't leave RegisterMove        */
12678     /* with the move registered when the conditions for registering no   */
12679     /* longer hold                                                       */
12680     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12681         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12682         nCmailMovesRegistered --;
12683
12684         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12685           {
12686               free(cmailCommentList[lastLoadGameNumber - 1]);
12687               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12688           }
12689     }
12690
12691     if (cmailOldMove == -1) {
12692         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12693         return FALSE;
12694     }
12695
12696     if (currentMove > cmailOldMove + 1) {
12697         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12698         return FALSE;
12699     }
12700
12701     if (currentMove < cmailOldMove) {
12702         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12703         return FALSE;
12704     }
12705
12706     if (forwardMostMove > currentMove) {
12707         /* Silently truncate extra moves */
12708         TruncateGame();
12709     }
12710
12711     if (   (currentMove == cmailOldMove + 1)
12712         || (   (currentMove == cmailOldMove)
12713             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12714                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12715         if (gameInfo.result != GameUnfinished) {
12716             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12717         }
12718
12719         if (commentList[currentMove] != NULL) {
12720             cmailCommentList[lastLoadGameNumber - 1]
12721               = StrSave(commentList[currentMove]);
12722         }
12723         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12724
12725         if (appData.debugMode)
12726           fprintf(debugFP, "Saving %s for game %d\n",
12727                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12728
12729         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12730
12731         f = fopen(string, "w");
12732         if (appData.oldSaveStyle) {
12733             SaveGameOldStyle(f); /* also closes the file */
12734
12735             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12736             f = fopen(string, "w");
12737             SavePosition(f, 0, NULL); /* also closes the file */
12738         } else {
12739             fprintf(f, "{--------------\n");
12740             PrintPosition(f, currentMove);
12741             fprintf(f, "--------------}\n\n");
12742
12743             SaveGame(f, 0, NULL); /* also closes the file*/
12744         }
12745
12746         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12747         nCmailMovesRegistered ++;
12748     } else if (nCmailGames == 1) {
12749         DisplayError(_("You have not made a move yet"), 0);
12750         return FALSE;
12751     }
12752
12753     return TRUE;
12754 }
12755
12756 void
12757 MailMoveEvent ()
12758 {
12759 #if !WIN32
12760     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12761     FILE *commandOutput;
12762     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12763     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12764     int nBuffers;
12765     int i;
12766     int archived;
12767     char *arcDir;
12768
12769     if (! cmailMsgLoaded) {
12770         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12771         return;
12772     }
12773
12774     if (nCmailGames == nCmailResults) {
12775         DisplayError(_("No unfinished games"), 0);
12776         return;
12777     }
12778
12779 #if CMAIL_PROHIBIT_REMAIL
12780     if (cmailMailedMove) {
12781       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);
12782         DisplayError(msg, 0);
12783         return;
12784     }
12785 #endif
12786
12787     if (! (cmailMailedMove || RegisterMove())) return;
12788
12789     if (   cmailMailedMove
12790         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12791       snprintf(string, MSG_SIZ, partCommandString,
12792                appData.debugMode ? " -v" : "", appData.cmailGameName);
12793         commandOutput = popen(string, "r");
12794
12795         if (commandOutput == NULL) {
12796             DisplayError(_("Failed to invoke cmail"), 0);
12797         } else {
12798             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12799                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12800             }
12801             if (nBuffers > 1) {
12802                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12803                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12804                 nBytes = MSG_SIZ - 1;
12805             } else {
12806                 (void) memcpy(msg, buffer, nBytes);
12807             }
12808             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12809
12810             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12811                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12812
12813                 archived = TRUE;
12814                 for (i = 0; i < nCmailGames; i ++) {
12815                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12816                         archived = FALSE;
12817                     }
12818                 }
12819                 if (   archived
12820                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12821                         != NULL)) {
12822                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12823                            arcDir,
12824                            appData.cmailGameName,
12825                            gameInfo.date);
12826                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12827                     cmailMsgLoaded = FALSE;
12828                 }
12829             }
12830
12831             DisplayInformation(msg);
12832             pclose(commandOutput);
12833         }
12834     } else {
12835         if ((*cmailMsg) != '\0') {
12836             DisplayInformation(cmailMsg);
12837         }
12838     }
12839
12840     return;
12841 #endif /* !WIN32 */
12842 }
12843
12844 char *
12845 CmailMsg ()
12846 {
12847 #if WIN32
12848     return NULL;
12849 #else
12850     int  prependComma = 0;
12851     char number[5];
12852     char string[MSG_SIZ];       /* Space for game-list */
12853     int  i;
12854
12855     if (!cmailMsgLoaded) return "";
12856
12857     if (cmailMailedMove) {
12858       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12859     } else {
12860         /* Create a list of games left */
12861       snprintf(string, MSG_SIZ, "[");
12862         for (i = 0; i < nCmailGames; i ++) {
12863             if (! (   cmailMoveRegistered[i]
12864                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12865                 if (prependComma) {
12866                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12867                 } else {
12868                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12869                     prependComma = 1;
12870                 }
12871
12872                 strcat(string, number);
12873             }
12874         }
12875         strcat(string, "]");
12876
12877         if (nCmailMovesRegistered + nCmailResults == 0) {
12878             switch (nCmailGames) {
12879               case 1:
12880                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12881                 break;
12882
12883               case 2:
12884                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12885                 break;
12886
12887               default:
12888                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12889                          nCmailGames);
12890                 break;
12891             }
12892         } else {
12893             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12894               case 1:
12895                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12896                          string);
12897                 break;
12898
12899               case 0:
12900                 if (nCmailResults == nCmailGames) {
12901                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12902                 } else {
12903                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12904                 }
12905                 break;
12906
12907               default:
12908                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12909                          string);
12910             }
12911         }
12912     }
12913     return cmailMsg;
12914 #endif /* WIN32 */
12915 }
12916
12917 void
12918 ResetGameEvent ()
12919 {
12920     if (gameMode == Training)
12921       SetTrainingModeOff();
12922
12923     Reset(TRUE, TRUE);
12924     cmailMsgLoaded = FALSE;
12925     if (appData.icsActive) {
12926       SendToICS(ics_prefix);
12927       SendToICS("refresh\n");
12928     }
12929 }
12930
12931 void
12932 ExitEvent (int status)
12933 {
12934     exiting++;
12935     if (exiting > 2) {
12936       /* Give up on clean exit */
12937       exit(status);
12938     }
12939     if (exiting > 1) {
12940       /* Keep trying for clean exit */
12941       return;
12942     }
12943
12944     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12945
12946     if (telnetISR != NULL) {
12947       RemoveInputSource(telnetISR);
12948     }
12949     if (icsPR != NoProc) {
12950       DestroyChildProcess(icsPR, TRUE);
12951     }
12952
12953     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12954     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12955
12956     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12957     /* make sure this other one finishes before killing it!                  */
12958     if(endingGame) { int count = 0;
12959         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12960         while(endingGame && count++ < 10) DoSleep(1);
12961         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12962     }
12963
12964     /* Kill off chess programs */
12965     if (first.pr != NoProc) {
12966         ExitAnalyzeMode();
12967
12968         DoSleep( appData.delayBeforeQuit );
12969         SendToProgram("quit\n", &first);
12970         DoSleep( appData.delayAfterQuit );
12971         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12972     }
12973     if (second.pr != NoProc) {
12974         DoSleep( appData.delayBeforeQuit );
12975         SendToProgram("quit\n", &second);
12976         DoSleep( appData.delayAfterQuit );
12977         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12978     }
12979     if (first.isr != NULL) {
12980         RemoveInputSource(first.isr);
12981     }
12982     if (second.isr != NULL) {
12983         RemoveInputSource(second.isr);
12984     }
12985
12986     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12987     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12988
12989     ShutDownFrontEnd();
12990     exit(status);
12991 }
12992
12993 void
12994 PauseEvent ()
12995 {
12996     if (appData.debugMode)
12997         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12998     if (pausing) {
12999         pausing = FALSE;
13000         ModeHighlight();
13001         if (gameMode == MachinePlaysWhite ||
13002             gameMode == MachinePlaysBlack) {
13003             StartClocks();
13004         } else {
13005             DisplayBothClocks();
13006         }
13007         if (gameMode == PlayFromGameFile) {
13008             if (appData.timeDelay >= 0)
13009                 AutoPlayGameLoop();
13010         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13011             Reset(FALSE, TRUE);
13012             SendToICS(ics_prefix);
13013             SendToICS("refresh\n");
13014         } else if (currentMove < forwardMostMove) {
13015             ForwardInner(forwardMostMove);
13016         }
13017         pauseExamInvalid = FALSE;
13018     } else {
13019         switch (gameMode) {
13020           default:
13021             return;
13022           case IcsExamining:
13023             pauseExamForwardMostMove = forwardMostMove;
13024             pauseExamInvalid = FALSE;
13025             /* fall through */
13026           case IcsObserving:
13027           case IcsPlayingWhite:
13028           case IcsPlayingBlack:
13029             pausing = TRUE;
13030             ModeHighlight();
13031             return;
13032           case PlayFromGameFile:
13033             (void) StopLoadGameTimer();
13034             pausing = TRUE;
13035             ModeHighlight();
13036             break;
13037           case BeginningOfGame:
13038             if (appData.icsActive) return;
13039             /* else fall through */
13040           case MachinePlaysWhite:
13041           case MachinePlaysBlack:
13042           case TwoMachinesPlay:
13043             if (forwardMostMove == 0)
13044               return;           /* don't pause if no one has moved */
13045             if ((gameMode == MachinePlaysWhite &&
13046                  !WhiteOnMove(forwardMostMove)) ||
13047                 (gameMode == MachinePlaysBlack &&
13048                  WhiteOnMove(forwardMostMove))) {
13049                 StopClocks();
13050             }
13051             pausing = TRUE;
13052             ModeHighlight();
13053             break;
13054         }
13055     }
13056 }
13057
13058 void
13059 EditCommentEvent ()
13060 {
13061     char title[MSG_SIZ];
13062
13063     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13064       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13065     } else {
13066       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13067                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13068                parseList[currentMove - 1]);
13069     }
13070
13071     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13072 }
13073
13074
13075 void
13076 EditTagsEvent ()
13077 {
13078     char *tags = PGNTags(&gameInfo);
13079     bookUp = FALSE;
13080     EditTagsPopUp(tags, NULL);
13081     free(tags);
13082 }
13083
13084 void
13085 AnalyzeModeEvent ()
13086 {
13087     if (appData.noChessProgram || gameMode == AnalyzeMode)
13088       return;
13089
13090     if (gameMode != AnalyzeFile) {
13091         if (!appData.icsEngineAnalyze) {
13092                EditGameEvent();
13093                if (gameMode != EditGame) return;
13094         }
13095         ResurrectChessProgram();
13096         SendToProgram("analyze\n", &first);
13097         first.analyzing = TRUE;
13098         /*first.maybeThinking = TRUE;*/
13099         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13100         EngineOutputPopUp();
13101     }
13102     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13103     pausing = FALSE;
13104     ModeHighlight();
13105     SetGameInfo();
13106
13107     StartAnalysisClock();
13108     GetTimeMark(&lastNodeCountTime);
13109     lastNodeCount = 0;
13110 }
13111
13112 void
13113 AnalyzeFileEvent ()
13114 {
13115     if (appData.noChessProgram || gameMode == AnalyzeFile)
13116       return;
13117
13118     if (gameMode != AnalyzeMode) {
13119         EditGameEvent();
13120         if (gameMode != EditGame) return;
13121         ResurrectChessProgram();
13122         SendToProgram("analyze\n", &first);
13123         first.analyzing = TRUE;
13124         /*first.maybeThinking = TRUE;*/
13125         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13126         EngineOutputPopUp();
13127     }
13128     gameMode = AnalyzeFile;
13129     pausing = FALSE;
13130     ModeHighlight();
13131     SetGameInfo();
13132
13133     StartAnalysisClock();
13134     GetTimeMark(&lastNodeCountTime);
13135     lastNodeCount = 0;
13136     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13137 }
13138
13139 void
13140 MachineWhiteEvent ()
13141 {
13142     char buf[MSG_SIZ];
13143     char *bookHit = NULL;
13144
13145     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13146       return;
13147
13148
13149     if (gameMode == PlayFromGameFile ||
13150         gameMode == TwoMachinesPlay  ||
13151         gameMode == Training         ||
13152         gameMode == AnalyzeMode      ||
13153         gameMode == EndOfGame)
13154         EditGameEvent();
13155
13156     if (gameMode == EditPosition)
13157         EditPositionDone(TRUE);
13158
13159     if (!WhiteOnMove(currentMove)) {
13160         DisplayError(_("It is not White's turn"), 0);
13161         return;
13162     }
13163
13164     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13165       ExitAnalyzeMode();
13166
13167     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13168         gameMode == AnalyzeFile)
13169         TruncateGame();
13170
13171     ResurrectChessProgram();    /* in case it isn't running */
13172     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13173         gameMode = MachinePlaysWhite;
13174         ResetClocks();
13175     } else
13176     gameMode = MachinePlaysWhite;
13177     pausing = FALSE;
13178     ModeHighlight();
13179     SetGameInfo();
13180     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13181     DisplayTitle(buf);
13182     if (first.sendName) {
13183       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13184       SendToProgram(buf, &first);
13185     }
13186     if (first.sendTime) {
13187       if (first.useColors) {
13188         SendToProgram("black\n", &first); /*gnu kludge*/
13189       }
13190       SendTimeRemaining(&first, TRUE);
13191     }
13192     if (first.useColors) {
13193       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13194     }
13195     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13196     SetMachineThinkingEnables();
13197     first.maybeThinking = TRUE;
13198     StartClocks();
13199     firstMove = FALSE;
13200
13201     if (appData.autoFlipView && !flipView) {
13202       flipView = !flipView;
13203       DrawPosition(FALSE, NULL);
13204       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13205     }
13206
13207     if(bookHit) { // [HGM] book: simulate book reply
13208         static char bookMove[MSG_SIZ]; // a bit generous?
13209
13210         programStats.nodes = programStats.depth = programStats.time =
13211         programStats.score = programStats.got_only_move = 0;
13212         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13213
13214         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13215         strcat(bookMove, bookHit);
13216         HandleMachineMove(bookMove, &first);
13217     }
13218 }
13219
13220 void
13221 MachineBlackEvent ()
13222 {
13223   char buf[MSG_SIZ];
13224   char *bookHit = NULL;
13225
13226     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13227         return;
13228
13229
13230     if (gameMode == PlayFromGameFile ||
13231         gameMode == TwoMachinesPlay  ||
13232         gameMode == Training         ||
13233         gameMode == AnalyzeMode      ||
13234         gameMode == EndOfGame)
13235         EditGameEvent();
13236
13237     if (gameMode == EditPosition)
13238         EditPositionDone(TRUE);
13239
13240     if (WhiteOnMove(currentMove)) {
13241         DisplayError(_("It is not Black's turn"), 0);
13242         return;
13243     }
13244
13245     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13246       ExitAnalyzeMode();
13247
13248     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13249         gameMode == AnalyzeFile)
13250         TruncateGame();
13251
13252     ResurrectChessProgram();    /* in case it isn't running */
13253     gameMode = MachinePlaysBlack;
13254     pausing = FALSE;
13255     ModeHighlight();
13256     SetGameInfo();
13257     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13258     DisplayTitle(buf);
13259     if (first.sendName) {
13260       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13261       SendToProgram(buf, &first);
13262     }
13263     if (first.sendTime) {
13264       if (first.useColors) {
13265         SendToProgram("white\n", &first); /*gnu kludge*/
13266       }
13267       SendTimeRemaining(&first, FALSE);
13268     }
13269     if (first.useColors) {
13270       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13271     }
13272     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13273     SetMachineThinkingEnables();
13274     first.maybeThinking = TRUE;
13275     StartClocks();
13276
13277     if (appData.autoFlipView && flipView) {
13278       flipView = !flipView;
13279       DrawPosition(FALSE, NULL);
13280       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13281     }
13282     if(bookHit) { // [HGM] book: simulate book reply
13283         static char bookMove[MSG_SIZ]; // a bit generous?
13284
13285         programStats.nodes = programStats.depth = programStats.time =
13286         programStats.score = programStats.got_only_move = 0;
13287         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13288
13289         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13290         strcat(bookMove, bookHit);
13291         HandleMachineMove(bookMove, &first);
13292     }
13293 }
13294
13295
13296 void
13297 DisplayTwoMachinesTitle ()
13298 {
13299     char buf[MSG_SIZ];
13300     if (appData.matchGames > 0) {
13301         if(appData.tourneyFile[0]) {
13302           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13303                    gameInfo.white, _("vs."), gameInfo.black,
13304                    nextGame+1, appData.matchGames+1,
13305                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13306         } else 
13307         if (first.twoMachinesColor[0] == 'w') {
13308           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13309                    gameInfo.white, _("vs."),  gameInfo.black,
13310                    first.matchWins, second.matchWins,
13311                    matchGame - 1 - (first.matchWins + second.matchWins));
13312         } else {
13313           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13314                    gameInfo.white, _("vs."), gameInfo.black,
13315                    second.matchWins, first.matchWins,
13316                    matchGame - 1 - (first.matchWins + second.matchWins));
13317         }
13318     } else {
13319       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13320     }
13321     DisplayTitle(buf);
13322 }
13323
13324 void
13325 SettingsMenuIfReady ()
13326 {
13327   if (second.lastPing != second.lastPong) {
13328     DisplayMessage("", _("Waiting for second chess program"));
13329     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13330     return;
13331   }
13332   ThawUI();
13333   DisplayMessage("", "");
13334   SettingsPopUp(&second);
13335 }
13336
13337 int
13338 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13339 {
13340     char buf[MSG_SIZ];
13341     if (cps->pr == NoProc) {
13342         StartChessProgram(cps);
13343         if (cps->protocolVersion == 1) {
13344           retry();
13345         } else {
13346           /* kludge: allow timeout for initial "feature" command */
13347           FreezeUI();
13348           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13349           DisplayMessage("", buf);
13350           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13351         }
13352         return 1;
13353     }
13354     return 0;
13355 }
13356
13357 void
13358 TwoMachinesEvent P((void))
13359 {
13360     int i;
13361     char buf[MSG_SIZ];
13362     ChessProgramState *onmove;
13363     char *bookHit = NULL;
13364     static int stalling = 0;
13365     TimeMark now;
13366     long wait;
13367
13368     if (appData.noChessProgram) return;
13369
13370     switch (gameMode) {
13371       case TwoMachinesPlay:
13372         return;
13373       case MachinePlaysWhite:
13374       case MachinePlaysBlack:
13375         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13376             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13377             return;
13378         }
13379         /* fall through */
13380       case BeginningOfGame:
13381       case PlayFromGameFile:
13382       case EndOfGame:
13383         EditGameEvent();
13384         if (gameMode != EditGame) return;
13385         break;
13386       case EditPosition:
13387         EditPositionDone(TRUE);
13388         break;
13389       case AnalyzeMode:
13390       case AnalyzeFile:
13391         ExitAnalyzeMode();
13392         break;
13393       case EditGame:
13394       default:
13395         break;
13396     }
13397
13398 //    forwardMostMove = currentMove;
13399     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13400
13401     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13402
13403     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13404     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13405       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13406       return;
13407     }
13408     if(!stalling) {
13409       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13410       SendToProgram("force\n", &second);
13411       stalling = 1;
13412       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13413       return;
13414     }
13415     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13416     if(appData.matchPause>10000 || appData.matchPause<10)
13417                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13418     wait = SubtractTimeMarks(&now, &pauseStart);
13419     if(wait < appData.matchPause) {
13420         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13421         return;
13422     }
13423     stalling = 0;
13424     DisplayMessage("", "");
13425     if (startedFromSetupPosition) {
13426         SendBoard(&second, backwardMostMove);
13427     if (appData.debugMode) {
13428         fprintf(debugFP, "Two Machines\n");
13429     }
13430     }
13431     for (i = backwardMostMove; i < forwardMostMove; i++) {
13432         SendMoveToProgram(i, &second);
13433     }
13434
13435     gameMode = TwoMachinesPlay;
13436     pausing = FALSE;
13437     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13438     SetGameInfo();
13439     DisplayTwoMachinesTitle();
13440     firstMove = TRUE;
13441     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13442         onmove = &first;
13443     } else {
13444         onmove = &second;
13445     }
13446     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13447     SendToProgram(first.computerString, &first);
13448     if (first.sendName) {
13449       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13450       SendToProgram(buf, &first);
13451     }
13452     SendToProgram(second.computerString, &second);
13453     if (second.sendName) {
13454       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13455       SendToProgram(buf, &second);
13456     }
13457
13458     ResetClocks();
13459     if (!first.sendTime || !second.sendTime) {
13460         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13461         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13462     }
13463     if (onmove->sendTime) {
13464       if (onmove->useColors) {
13465         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13466       }
13467       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13468     }
13469     if (onmove->useColors) {
13470       SendToProgram(onmove->twoMachinesColor, onmove);
13471     }
13472     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13473 //    SendToProgram("go\n", onmove);
13474     onmove->maybeThinking = TRUE;
13475     SetMachineThinkingEnables();
13476
13477     StartClocks();
13478
13479     if(bookHit) { // [HGM] book: simulate book reply
13480         static char bookMove[MSG_SIZ]; // a bit generous?
13481
13482         programStats.nodes = programStats.depth = programStats.time =
13483         programStats.score = programStats.got_only_move = 0;
13484         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13485
13486         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13487         strcat(bookMove, bookHit);
13488         savedMessage = bookMove; // args for deferred call
13489         savedState = onmove;
13490         ScheduleDelayedEvent(DeferredBookMove, 1);
13491     }
13492 }
13493
13494 void
13495 TrainingEvent ()
13496 {
13497     if (gameMode == Training) {
13498       SetTrainingModeOff();
13499       gameMode = PlayFromGameFile;
13500       DisplayMessage("", _("Training mode off"));
13501     } else {
13502       gameMode = Training;
13503       animateTraining = appData.animate;
13504
13505       /* make sure we are not already at the end of the game */
13506       if (currentMove < forwardMostMove) {
13507         SetTrainingModeOn();
13508         DisplayMessage("", _("Training mode on"));
13509       } else {
13510         gameMode = PlayFromGameFile;
13511         DisplayError(_("Already at end of game"), 0);
13512       }
13513     }
13514     ModeHighlight();
13515 }
13516
13517 void
13518 IcsClientEvent ()
13519 {
13520     if (!appData.icsActive) return;
13521     switch (gameMode) {
13522       case IcsPlayingWhite:
13523       case IcsPlayingBlack:
13524       case IcsObserving:
13525       case IcsIdle:
13526       case BeginningOfGame:
13527       case IcsExamining:
13528         return;
13529
13530       case EditGame:
13531         break;
13532
13533       case EditPosition:
13534         EditPositionDone(TRUE);
13535         break;
13536
13537       case AnalyzeMode:
13538       case AnalyzeFile:
13539         ExitAnalyzeMode();
13540         break;
13541
13542       default:
13543         EditGameEvent();
13544         break;
13545     }
13546
13547     gameMode = IcsIdle;
13548     ModeHighlight();
13549     return;
13550 }
13551
13552 void
13553 EditGameEvent ()
13554 {
13555     int i;
13556
13557     switch (gameMode) {
13558       case Training:
13559         SetTrainingModeOff();
13560         break;
13561       case MachinePlaysWhite:
13562       case MachinePlaysBlack:
13563       case BeginningOfGame:
13564         SendToProgram("force\n", &first);
13565         SetUserThinkingEnables();
13566         break;
13567       case PlayFromGameFile:
13568         (void) StopLoadGameTimer();
13569         if (gameFileFP != NULL) {
13570             gameFileFP = NULL;
13571         }
13572         break;
13573       case EditPosition:
13574         EditPositionDone(TRUE);
13575         break;
13576       case AnalyzeMode:
13577       case AnalyzeFile:
13578         ExitAnalyzeMode();
13579         SendToProgram("force\n", &first);
13580         break;
13581       case TwoMachinesPlay:
13582         GameEnds(EndOfFile, NULL, GE_PLAYER);
13583         ResurrectChessProgram();
13584         SetUserThinkingEnables();
13585         break;
13586       case EndOfGame:
13587         ResurrectChessProgram();
13588         break;
13589       case IcsPlayingBlack:
13590       case IcsPlayingWhite:
13591         DisplayError(_("Warning: You are still playing a game"), 0);
13592         break;
13593       case IcsObserving:
13594         DisplayError(_("Warning: You are still observing a game"), 0);
13595         break;
13596       case IcsExamining:
13597         DisplayError(_("Warning: You are still examining a game"), 0);
13598         break;
13599       case IcsIdle:
13600         break;
13601       case EditGame:
13602       default:
13603         return;
13604     }
13605
13606     pausing = FALSE;
13607     StopClocks();
13608     first.offeredDraw = second.offeredDraw = 0;
13609
13610     if (gameMode == PlayFromGameFile) {
13611         whiteTimeRemaining = timeRemaining[0][currentMove];
13612         blackTimeRemaining = timeRemaining[1][currentMove];
13613         DisplayTitle("");
13614     }
13615
13616     if (gameMode == MachinePlaysWhite ||
13617         gameMode == MachinePlaysBlack ||
13618         gameMode == TwoMachinesPlay ||
13619         gameMode == EndOfGame) {
13620         i = forwardMostMove;
13621         while (i > currentMove) {
13622             SendToProgram("undo\n", &first);
13623             i--;
13624         }
13625         if(!adjustedClock) {
13626         whiteTimeRemaining = timeRemaining[0][currentMove];
13627         blackTimeRemaining = timeRemaining[1][currentMove];
13628         DisplayBothClocks();
13629         }
13630         if (whiteFlag || blackFlag) {
13631             whiteFlag = blackFlag = 0;
13632         }
13633         DisplayTitle("");
13634     }
13635
13636     gameMode = EditGame;
13637     ModeHighlight();
13638     SetGameInfo();
13639 }
13640
13641
13642 void
13643 EditPositionEvent ()
13644 {
13645     if (gameMode == EditPosition) {
13646         EditGameEvent();
13647         return;
13648     }
13649
13650     EditGameEvent();
13651     if (gameMode != EditGame) return;
13652
13653     gameMode = EditPosition;
13654     ModeHighlight();
13655     SetGameInfo();
13656     if (currentMove > 0)
13657       CopyBoard(boards[0], boards[currentMove]);
13658
13659     blackPlaysFirst = !WhiteOnMove(currentMove);
13660     ResetClocks();
13661     currentMove = forwardMostMove = backwardMostMove = 0;
13662     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13663     DisplayMove(-1);
13664 }
13665
13666 void
13667 ExitAnalyzeMode ()
13668 {
13669     /* [DM] icsEngineAnalyze - possible call from other functions */
13670     if (appData.icsEngineAnalyze) {
13671         appData.icsEngineAnalyze = FALSE;
13672
13673         DisplayMessage("",_("Close ICS engine analyze..."));
13674     }
13675     if (first.analysisSupport && first.analyzing) {
13676       SendToProgram("exit\n", &first);
13677       first.analyzing = FALSE;
13678     }
13679     thinkOutput[0] = NULLCHAR;
13680 }
13681
13682 void
13683 EditPositionDone (Boolean fakeRights)
13684 {
13685     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13686
13687     startedFromSetupPosition = TRUE;
13688     InitChessProgram(&first, FALSE);
13689     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13690       boards[0][EP_STATUS] = EP_NONE;
13691       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13692     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13693         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13694         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13695       } else boards[0][CASTLING][2] = NoRights;
13696     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13697         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13698         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13699       } else boards[0][CASTLING][5] = NoRights;
13700     }
13701     SendToProgram("force\n", &first);
13702     if (blackPlaysFirst) {
13703         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13704         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13705         currentMove = forwardMostMove = backwardMostMove = 1;
13706         CopyBoard(boards[1], boards[0]);
13707     } else {
13708         currentMove = forwardMostMove = backwardMostMove = 0;
13709     }
13710     SendBoard(&first, forwardMostMove);
13711     if (appData.debugMode) {
13712         fprintf(debugFP, "EditPosDone\n");
13713     }
13714     DisplayTitle("");
13715     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13716     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13717     gameMode = EditGame;
13718     ModeHighlight();
13719     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13720     ClearHighlights(); /* [AS] */
13721 }
13722
13723 /* Pause for `ms' milliseconds */
13724 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13725 void
13726 TimeDelay (long ms)
13727 {
13728     TimeMark m1, m2;
13729
13730     GetTimeMark(&m1);
13731     do {
13732         GetTimeMark(&m2);
13733     } while (SubtractTimeMarks(&m2, &m1) < ms);
13734 }
13735
13736 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13737 void
13738 SendMultiLineToICS (char *buf)
13739 {
13740     char temp[MSG_SIZ+1], *p;
13741     int len;
13742
13743     len = strlen(buf);
13744     if (len > MSG_SIZ)
13745       len = MSG_SIZ;
13746
13747     strncpy(temp, buf, len);
13748     temp[len] = 0;
13749
13750     p = temp;
13751     while (*p) {
13752         if (*p == '\n' || *p == '\r')
13753           *p = ' ';
13754         ++p;
13755     }
13756
13757     strcat(temp, "\n");
13758     SendToICS(temp);
13759     SendToPlayer(temp, strlen(temp));
13760 }
13761
13762 void
13763 SetWhiteToPlayEvent ()
13764 {
13765     if (gameMode == EditPosition) {
13766         blackPlaysFirst = FALSE;
13767         DisplayBothClocks();    /* works because currentMove is 0 */
13768     } else if (gameMode == IcsExamining) {
13769         SendToICS(ics_prefix);
13770         SendToICS("tomove white\n");
13771     }
13772 }
13773
13774 void
13775 SetBlackToPlayEvent ()
13776 {
13777     if (gameMode == EditPosition) {
13778         blackPlaysFirst = TRUE;
13779         currentMove = 1;        /* kludge */
13780         DisplayBothClocks();
13781         currentMove = 0;
13782     } else if (gameMode == IcsExamining) {
13783         SendToICS(ics_prefix);
13784         SendToICS("tomove black\n");
13785     }
13786 }
13787
13788 void
13789 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13790 {
13791     char buf[MSG_SIZ];
13792     ChessSquare piece = boards[0][y][x];
13793
13794     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13795
13796     switch (selection) {
13797       case ClearBoard:
13798         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13799             SendToICS(ics_prefix);
13800             SendToICS("bsetup clear\n");
13801         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13802             SendToICS(ics_prefix);
13803             SendToICS("clearboard\n");
13804         } else {
13805             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13806                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13807                 for (y = 0; y < BOARD_HEIGHT; y++) {
13808                     if (gameMode == IcsExamining) {
13809                         if (boards[currentMove][y][x] != EmptySquare) {
13810                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13811                                     AAA + x, ONE + y);
13812                             SendToICS(buf);
13813                         }
13814                     } else {
13815                         boards[0][y][x] = p;
13816                     }
13817                 }
13818             }
13819         }
13820         if (gameMode == EditPosition) {
13821             DrawPosition(FALSE, boards[0]);
13822         }
13823         break;
13824
13825       case WhitePlay:
13826         SetWhiteToPlayEvent();
13827         break;
13828
13829       case BlackPlay:
13830         SetBlackToPlayEvent();
13831         break;
13832
13833       case EmptySquare:
13834         if (gameMode == IcsExamining) {
13835             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13836             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13837             SendToICS(buf);
13838         } else {
13839             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13840                 if(x == BOARD_LEFT-2) {
13841                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13842                     boards[0][y][1] = 0;
13843                 } else
13844                 if(x == BOARD_RGHT+1) {
13845                     if(y >= gameInfo.holdingsSize) break;
13846                     boards[0][y][BOARD_WIDTH-2] = 0;
13847                 } else break;
13848             }
13849             boards[0][y][x] = EmptySquare;
13850             DrawPosition(FALSE, boards[0]);
13851         }
13852         break;
13853
13854       case PromotePiece:
13855         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13856            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13857             selection = (ChessSquare) (PROMOTED piece);
13858         } else if(piece == EmptySquare) selection = WhiteSilver;
13859         else selection = (ChessSquare)((int)piece - 1);
13860         goto defaultlabel;
13861
13862       case DemotePiece:
13863         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13864            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13865             selection = (ChessSquare) (DEMOTED piece);
13866         } else if(piece == EmptySquare) selection = BlackSilver;
13867         else selection = (ChessSquare)((int)piece + 1);
13868         goto defaultlabel;
13869
13870       case WhiteQueen:
13871       case BlackQueen:
13872         if(gameInfo.variant == VariantShatranj ||
13873            gameInfo.variant == VariantXiangqi  ||
13874            gameInfo.variant == VariantCourier  ||
13875            gameInfo.variant == VariantMakruk     )
13876             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13877         goto defaultlabel;
13878
13879       case WhiteKing:
13880       case BlackKing:
13881         if(gameInfo.variant == VariantXiangqi)
13882             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13883         if(gameInfo.variant == VariantKnightmate)
13884             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13885       default:
13886         defaultlabel:
13887         if (gameMode == IcsExamining) {
13888             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13889             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13890                      PieceToChar(selection), AAA + x, ONE + y);
13891             SendToICS(buf);
13892         } else {
13893             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13894                 int n;
13895                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13896                     n = PieceToNumber(selection - BlackPawn);
13897                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13898                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13899                     boards[0][BOARD_HEIGHT-1-n][1]++;
13900                 } else
13901                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13902                     n = PieceToNumber(selection);
13903                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13904                     boards[0][n][BOARD_WIDTH-1] = selection;
13905                     boards[0][n][BOARD_WIDTH-2]++;
13906                 }
13907             } else
13908             boards[0][y][x] = selection;
13909             DrawPosition(TRUE, boards[0]);
13910         }
13911         break;
13912     }
13913 }
13914
13915
13916 void
13917 DropMenuEvent (ChessSquare selection, int x, int y)
13918 {
13919     ChessMove moveType;
13920
13921     switch (gameMode) {
13922       case IcsPlayingWhite:
13923       case MachinePlaysBlack:
13924         if (!WhiteOnMove(currentMove)) {
13925             DisplayMoveError(_("It is Black's turn"));
13926             return;
13927         }
13928         moveType = WhiteDrop;
13929         break;
13930       case IcsPlayingBlack:
13931       case MachinePlaysWhite:
13932         if (WhiteOnMove(currentMove)) {
13933             DisplayMoveError(_("It is White's turn"));
13934             return;
13935         }
13936         moveType = BlackDrop;
13937         break;
13938       case EditGame:
13939         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13940         break;
13941       default:
13942         return;
13943     }
13944
13945     if (moveType == BlackDrop && selection < BlackPawn) {
13946       selection = (ChessSquare) ((int) selection
13947                                  + (int) BlackPawn - (int) WhitePawn);
13948     }
13949     if (boards[currentMove][y][x] != EmptySquare) {
13950         DisplayMoveError(_("That square is occupied"));
13951         return;
13952     }
13953
13954     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13955 }
13956
13957 void
13958 AcceptEvent ()
13959 {
13960     /* Accept a pending offer of any kind from opponent */
13961
13962     if (appData.icsActive) {
13963         SendToICS(ics_prefix);
13964         SendToICS("accept\n");
13965     } else if (cmailMsgLoaded) {
13966         if (currentMove == cmailOldMove &&
13967             commentList[cmailOldMove] != NULL &&
13968             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13969                    "Black offers a draw" : "White offers a draw")) {
13970             TruncateGame();
13971             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13972             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13973         } else {
13974             DisplayError(_("There is no pending offer on this move"), 0);
13975             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13976         }
13977     } else {
13978         /* Not used for offers from chess program */
13979     }
13980 }
13981
13982 void
13983 DeclineEvent ()
13984 {
13985     /* Decline a pending offer of any kind from opponent */
13986
13987     if (appData.icsActive) {
13988         SendToICS(ics_prefix);
13989         SendToICS("decline\n");
13990     } else if (cmailMsgLoaded) {
13991         if (currentMove == cmailOldMove &&
13992             commentList[cmailOldMove] != NULL &&
13993             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13994                    "Black offers a draw" : "White offers a draw")) {
13995 #ifdef NOTDEF
13996             AppendComment(cmailOldMove, "Draw declined", TRUE);
13997             DisplayComment(cmailOldMove - 1, "Draw declined");
13998 #endif /*NOTDEF*/
13999         } else {
14000             DisplayError(_("There is no pending offer on this move"), 0);
14001         }
14002     } else {
14003         /* Not used for offers from chess program */
14004     }
14005 }
14006
14007 void
14008 RematchEvent ()
14009 {
14010     /* Issue ICS rematch command */
14011     if (appData.icsActive) {
14012         SendToICS(ics_prefix);
14013         SendToICS("rematch\n");
14014     }
14015 }
14016
14017 void
14018 CallFlagEvent ()
14019 {
14020     /* Call your opponent's flag (claim a win on time) */
14021     if (appData.icsActive) {
14022         SendToICS(ics_prefix);
14023         SendToICS("flag\n");
14024     } else {
14025         switch (gameMode) {
14026           default:
14027             return;
14028           case MachinePlaysWhite:
14029             if (whiteFlag) {
14030                 if (blackFlag)
14031                   GameEnds(GameIsDrawn, "Both players ran out of time",
14032                            GE_PLAYER);
14033                 else
14034                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14035             } else {
14036                 DisplayError(_("Your opponent is not out of time"), 0);
14037             }
14038             break;
14039           case MachinePlaysBlack:
14040             if (blackFlag) {
14041                 if (whiteFlag)
14042                   GameEnds(GameIsDrawn, "Both players ran out of time",
14043                            GE_PLAYER);
14044                 else
14045                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14046             } else {
14047                 DisplayError(_("Your opponent is not out of time"), 0);
14048             }
14049             break;
14050         }
14051     }
14052 }
14053
14054 void
14055 ClockClick (int which)
14056 {       // [HGM] code moved to back-end from winboard.c
14057         if(which) { // black clock
14058           if (gameMode == EditPosition || gameMode == IcsExamining) {
14059             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14060             SetBlackToPlayEvent();
14061           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14062           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14063           } else if (shiftKey) {
14064             AdjustClock(which, -1);
14065           } else if (gameMode == IcsPlayingWhite ||
14066                      gameMode == MachinePlaysBlack) {
14067             CallFlagEvent();
14068           }
14069         } else { // white clock
14070           if (gameMode == EditPosition || gameMode == IcsExamining) {
14071             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14072             SetWhiteToPlayEvent();
14073           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14074           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14075           } else if (shiftKey) {
14076             AdjustClock(which, -1);
14077           } else if (gameMode == IcsPlayingBlack ||
14078                    gameMode == MachinePlaysWhite) {
14079             CallFlagEvent();
14080           }
14081         }
14082 }
14083
14084 void
14085 DrawEvent ()
14086 {
14087     /* Offer draw or accept pending draw offer from opponent */
14088
14089     if (appData.icsActive) {
14090         /* Note: tournament rules require draw offers to be
14091            made after you make your move but before you punch
14092            your clock.  Currently ICS doesn't let you do that;
14093            instead, you immediately punch your clock after making
14094            a move, but you can offer a draw at any time. */
14095
14096         SendToICS(ics_prefix);
14097         SendToICS("draw\n");
14098         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14099     } else if (cmailMsgLoaded) {
14100         if (currentMove == cmailOldMove &&
14101             commentList[cmailOldMove] != NULL &&
14102             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14103                    "Black offers a draw" : "White offers a draw")) {
14104             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14105             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14106         } else if (currentMove == cmailOldMove + 1) {
14107             char *offer = WhiteOnMove(cmailOldMove) ?
14108               "White offers a draw" : "Black offers a draw";
14109             AppendComment(currentMove, offer, TRUE);
14110             DisplayComment(currentMove - 1, offer);
14111             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14112         } else {
14113             DisplayError(_("You must make your move before offering a draw"), 0);
14114             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14115         }
14116     } else if (first.offeredDraw) {
14117         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14118     } else {
14119         if (first.sendDrawOffers) {
14120             SendToProgram("draw\n", &first);
14121             userOfferedDraw = TRUE;
14122         }
14123     }
14124 }
14125
14126 void
14127 AdjournEvent ()
14128 {
14129     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14130
14131     if (appData.icsActive) {
14132         SendToICS(ics_prefix);
14133         SendToICS("adjourn\n");
14134     } else {
14135         /* Currently GNU Chess doesn't offer or accept Adjourns */
14136     }
14137 }
14138
14139
14140 void
14141 AbortEvent ()
14142 {
14143     /* Offer Abort or accept pending Abort offer from opponent */
14144
14145     if (appData.icsActive) {
14146         SendToICS(ics_prefix);
14147         SendToICS("abort\n");
14148     } else {
14149         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14150     }
14151 }
14152
14153 void
14154 ResignEvent ()
14155 {
14156     /* Resign.  You can do this even if it's not your turn. */
14157
14158     if (appData.icsActive) {
14159         SendToICS(ics_prefix);
14160         SendToICS("resign\n");
14161     } else {
14162         switch (gameMode) {
14163           case MachinePlaysWhite:
14164             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14165             break;
14166           case MachinePlaysBlack:
14167             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14168             break;
14169           case EditGame:
14170             if (cmailMsgLoaded) {
14171                 TruncateGame();
14172                 if (WhiteOnMove(cmailOldMove)) {
14173                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14174                 } else {
14175                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14176                 }
14177                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14178             }
14179             break;
14180           default:
14181             break;
14182         }
14183     }
14184 }
14185
14186
14187 void
14188 StopObservingEvent ()
14189 {
14190     /* Stop observing current games */
14191     SendToICS(ics_prefix);
14192     SendToICS("unobserve\n");
14193 }
14194
14195 void
14196 StopExaminingEvent ()
14197 {
14198     /* Stop observing current game */
14199     SendToICS(ics_prefix);
14200     SendToICS("unexamine\n");
14201 }
14202
14203 void
14204 ForwardInner (int target)
14205 {
14206     int limit;
14207
14208     if (appData.debugMode)
14209         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14210                 target, currentMove, forwardMostMove);
14211
14212     if (gameMode == EditPosition)
14213       return;
14214
14215     seekGraphUp = FALSE;
14216     MarkTargetSquares(1);
14217
14218     if (gameMode == PlayFromGameFile && !pausing)
14219       PauseEvent();
14220
14221     if (gameMode == IcsExamining && pausing)
14222       limit = pauseExamForwardMostMove;
14223     else
14224       limit = forwardMostMove;
14225
14226     if (target > limit) target = limit;
14227
14228     if (target > 0 && moveList[target - 1][0]) {
14229         int fromX, fromY, toX, toY;
14230         toX = moveList[target - 1][2] - AAA;
14231         toY = moveList[target - 1][3] - ONE;
14232         if (moveList[target - 1][1] == '@') {
14233             if (appData.highlightLastMove) {
14234                 SetHighlights(-1, -1, toX, toY);
14235             }
14236         } else {
14237             fromX = moveList[target - 1][0] - AAA;
14238             fromY = moveList[target - 1][1] - ONE;
14239             if (target == currentMove + 1) {
14240                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14241             }
14242             if (appData.highlightLastMove) {
14243                 SetHighlights(fromX, fromY, toX, toY);
14244             }
14245         }
14246     }
14247     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14248         gameMode == Training || gameMode == PlayFromGameFile ||
14249         gameMode == AnalyzeFile) {
14250         while (currentMove < target) {
14251             SendMoveToProgram(currentMove++, &first);
14252         }
14253     } else {
14254         currentMove = target;
14255     }
14256
14257     if (gameMode == EditGame || gameMode == EndOfGame) {
14258         whiteTimeRemaining = timeRemaining[0][currentMove];
14259         blackTimeRemaining = timeRemaining[1][currentMove];
14260     }
14261     DisplayBothClocks();
14262     DisplayMove(currentMove - 1);
14263     DrawPosition(FALSE, boards[currentMove]);
14264     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14265     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14266         DisplayComment(currentMove - 1, commentList[currentMove]);
14267     }
14268 }
14269
14270
14271 void
14272 ForwardEvent ()
14273 {
14274     if (gameMode == IcsExamining && !pausing) {
14275         SendToICS(ics_prefix);
14276         SendToICS("forward\n");
14277     } else {
14278         ForwardInner(currentMove + 1);
14279     }
14280 }
14281
14282 void
14283 ToEndEvent ()
14284 {
14285     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14286         /* to optimze, we temporarily turn off analysis mode while we feed
14287          * the remaining moves to the engine. Otherwise we get analysis output
14288          * after each move.
14289          */
14290         if (first.analysisSupport) {
14291           SendToProgram("exit\nforce\n", &first);
14292           first.analyzing = FALSE;
14293         }
14294     }
14295
14296     if (gameMode == IcsExamining && !pausing) {
14297         SendToICS(ics_prefix);
14298         SendToICS("forward 999999\n");
14299     } else {
14300         ForwardInner(forwardMostMove);
14301     }
14302
14303     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14304         /* we have fed all the moves, so reactivate analysis mode */
14305         SendToProgram("analyze\n", &first);
14306         first.analyzing = TRUE;
14307         /*first.maybeThinking = TRUE;*/
14308         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14309     }
14310 }
14311
14312 void
14313 BackwardInner (int target)
14314 {
14315     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14316
14317     if (appData.debugMode)
14318         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14319                 target, currentMove, forwardMostMove);
14320
14321     if (gameMode == EditPosition) return;
14322     seekGraphUp = FALSE;
14323     MarkTargetSquares(1);
14324     if (currentMove <= backwardMostMove) {
14325         ClearHighlights();
14326         DrawPosition(full_redraw, boards[currentMove]);
14327         return;
14328     }
14329     if (gameMode == PlayFromGameFile && !pausing)
14330       PauseEvent();
14331
14332     if (moveList[target][0]) {
14333         int fromX, fromY, toX, toY;
14334         toX = moveList[target][2] - AAA;
14335         toY = moveList[target][3] - ONE;
14336         if (moveList[target][1] == '@') {
14337             if (appData.highlightLastMove) {
14338                 SetHighlights(-1, -1, toX, toY);
14339             }
14340         } else {
14341             fromX = moveList[target][0] - AAA;
14342             fromY = moveList[target][1] - ONE;
14343             if (target == currentMove - 1) {
14344                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14345             }
14346             if (appData.highlightLastMove) {
14347                 SetHighlights(fromX, fromY, toX, toY);
14348             }
14349         }
14350     }
14351     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14352         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14353         while (currentMove > target) {
14354             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14355                 // null move cannot be undone. Reload program with move history before it.
14356                 int i;
14357                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14358                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14359                 }
14360                 SendBoard(&first, i); 
14361                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14362                 break;
14363             }
14364             SendToProgram("undo\n", &first);
14365             currentMove--;
14366         }
14367     } else {
14368         currentMove = target;
14369     }
14370
14371     if (gameMode == EditGame || gameMode == EndOfGame) {
14372         whiteTimeRemaining = timeRemaining[0][currentMove];
14373         blackTimeRemaining = timeRemaining[1][currentMove];
14374     }
14375     DisplayBothClocks();
14376     DisplayMove(currentMove - 1);
14377     DrawPosition(full_redraw, boards[currentMove]);
14378     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14379     // [HGM] PV info: routine tests if comment empty
14380     DisplayComment(currentMove - 1, commentList[currentMove]);
14381 }
14382
14383 void
14384 BackwardEvent ()
14385 {
14386     if (gameMode == IcsExamining && !pausing) {
14387         SendToICS(ics_prefix);
14388         SendToICS("backward\n");
14389     } else {
14390         BackwardInner(currentMove - 1);
14391     }
14392 }
14393
14394 void
14395 ToStartEvent ()
14396 {
14397     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14398         /* to optimize, we temporarily turn off analysis mode while we undo
14399          * all the moves. Otherwise we get analysis output after each undo.
14400          */
14401         if (first.analysisSupport) {
14402           SendToProgram("exit\nforce\n", &first);
14403           first.analyzing = FALSE;
14404         }
14405     }
14406
14407     if (gameMode == IcsExamining && !pausing) {
14408         SendToICS(ics_prefix);
14409         SendToICS("backward 999999\n");
14410     } else {
14411         BackwardInner(backwardMostMove);
14412     }
14413
14414     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14415         /* we have fed all the moves, so reactivate analysis mode */
14416         SendToProgram("analyze\n", &first);
14417         first.analyzing = TRUE;
14418         /*first.maybeThinking = TRUE;*/
14419         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14420     }
14421 }
14422
14423 void
14424 ToNrEvent (int to)
14425 {
14426   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14427   if (to >= forwardMostMove) to = forwardMostMove;
14428   if (to <= backwardMostMove) to = backwardMostMove;
14429   if (to < currentMove) {
14430     BackwardInner(to);
14431   } else {
14432     ForwardInner(to);
14433   }
14434 }
14435
14436 void
14437 RevertEvent (Boolean annotate)
14438 {
14439     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14440         return;
14441     }
14442     if (gameMode != IcsExamining) {
14443         DisplayError(_("You are not examining a game"), 0);
14444         return;
14445     }
14446     if (pausing) {
14447         DisplayError(_("You can't revert while pausing"), 0);
14448         return;
14449     }
14450     SendToICS(ics_prefix);
14451     SendToICS("revert\n");
14452 }
14453
14454 void
14455 RetractMoveEvent ()
14456 {
14457     switch (gameMode) {
14458       case MachinePlaysWhite:
14459       case MachinePlaysBlack:
14460         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14461             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14462             return;
14463         }
14464         if (forwardMostMove < 2) return;
14465         currentMove = forwardMostMove = forwardMostMove - 2;
14466         whiteTimeRemaining = timeRemaining[0][currentMove];
14467         blackTimeRemaining = timeRemaining[1][currentMove];
14468         DisplayBothClocks();
14469         DisplayMove(currentMove - 1);
14470         ClearHighlights();/*!! could figure this out*/
14471         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14472         SendToProgram("remove\n", &first);
14473         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14474         break;
14475
14476       case BeginningOfGame:
14477       default:
14478         break;
14479
14480       case IcsPlayingWhite:
14481       case IcsPlayingBlack:
14482         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14483             SendToICS(ics_prefix);
14484             SendToICS("takeback 2\n");
14485         } else {
14486             SendToICS(ics_prefix);
14487             SendToICS("takeback 1\n");
14488         }
14489         break;
14490     }
14491 }
14492
14493 void
14494 MoveNowEvent ()
14495 {
14496     ChessProgramState *cps;
14497
14498     switch (gameMode) {
14499       case MachinePlaysWhite:
14500         if (!WhiteOnMove(forwardMostMove)) {
14501             DisplayError(_("It is your turn"), 0);
14502             return;
14503         }
14504         cps = &first;
14505         break;
14506       case MachinePlaysBlack:
14507         if (WhiteOnMove(forwardMostMove)) {
14508             DisplayError(_("It is your turn"), 0);
14509             return;
14510         }
14511         cps = &first;
14512         break;
14513       case TwoMachinesPlay:
14514         if (WhiteOnMove(forwardMostMove) ==
14515             (first.twoMachinesColor[0] == 'w')) {
14516             cps = &first;
14517         } else {
14518             cps = &second;
14519         }
14520         break;
14521       case BeginningOfGame:
14522       default:
14523         return;
14524     }
14525     SendToProgram("?\n", cps);
14526 }
14527
14528 void
14529 TruncateGameEvent ()
14530 {
14531     EditGameEvent();
14532     if (gameMode != EditGame) return;
14533     TruncateGame();
14534 }
14535
14536 void
14537 TruncateGame ()
14538 {
14539     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14540     if (forwardMostMove > currentMove) {
14541         if (gameInfo.resultDetails != NULL) {
14542             free(gameInfo.resultDetails);
14543             gameInfo.resultDetails = NULL;
14544             gameInfo.result = GameUnfinished;
14545         }
14546         forwardMostMove = currentMove;
14547         HistorySet(parseList, backwardMostMove, forwardMostMove,
14548                    currentMove-1);
14549     }
14550 }
14551
14552 void
14553 HintEvent ()
14554 {
14555     if (appData.noChessProgram) return;
14556     switch (gameMode) {
14557       case MachinePlaysWhite:
14558         if (WhiteOnMove(forwardMostMove)) {
14559             DisplayError(_("Wait until your turn"), 0);
14560             return;
14561         }
14562         break;
14563       case BeginningOfGame:
14564       case MachinePlaysBlack:
14565         if (!WhiteOnMove(forwardMostMove)) {
14566             DisplayError(_("Wait until your turn"), 0);
14567             return;
14568         }
14569         break;
14570       default:
14571         DisplayError(_("No hint available"), 0);
14572         return;
14573     }
14574     SendToProgram("hint\n", &first);
14575     hintRequested = TRUE;
14576 }
14577
14578 void
14579 BookEvent ()
14580 {
14581     if (appData.noChessProgram) return;
14582     switch (gameMode) {
14583       case MachinePlaysWhite:
14584         if (WhiteOnMove(forwardMostMove)) {
14585             DisplayError(_("Wait until your turn"), 0);
14586             return;
14587         }
14588         break;
14589       case BeginningOfGame:
14590       case MachinePlaysBlack:
14591         if (!WhiteOnMove(forwardMostMove)) {
14592             DisplayError(_("Wait until your turn"), 0);
14593             return;
14594         }
14595         break;
14596       case EditPosition:
14597         EditPositionDone(TRUE);
14598         break;
14599       case TwoMachinesPlay:
14600         return;
14601       default:
14602         break;
14603     }
14604     SendToProgram("bk\n", &first);
14605     bookOutput[0] = NULLCHAR;
14606     bookRequested = TRUE;
14607 }
14608
14609 void
14610 AboutGameEvent ()
14611 {
14612     char *tags = PGNTags(&gameInfo);
14613     TagsPopUp(tags, CmailMsg());
14614     free(tags);
14615 }
14616
14617 /* end button procedures */
14618
14619 void
14620 PrintPosition (FILE *fp, int move)
14621 {
14622     int i, j;
14623
14624     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14625         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14626             char c = PieceToChar(boards[move][i][j]);
14627             fputc(c == 'x' ? '.' : c, fp);
14628             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14629         }
14630     }
14631     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14632       fprintf(fp, "white to play\n");
14633     else
14634       fprintf(fp, "black to play\n");
14635 }
14636
14637 void
14638 PrintOpponents (FILE *fp)
14639 {
14640     if (gameInfo.white != NULL) {
14641         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14642     } else {
14643         fprintf(fp, "\n");
14644     }
14645 }
14646
14647 /* Find last component of program's own name, using some heuristics */
14648 void
14649 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14650 {
14651     char *p, *q;
14652     int local = (strcmp(host, "localhost") == 0);
14653     while (!local && (p = strchr(prog, ';')) != NULL) {
14654         p++;
14655         while (*p == ' ') p++;
14656         prog = p;
14657     }
14658     if (*prog == '"' || *prog == '\'') {
14659         q = strchr(prog + 1, *prog);
14660     } else {
14661         q = strchr(prog, ' ');
14662     }
14663     if (q == NULL) q = prog + strlen(prog);
14664     p = q;
14665     while (p >= prog && *p != '/' && *p != '\\') p--;
14666     p++;
14667     if(p == prog && *p == '"') p++;
14668     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14669     memcpy(buf, p, q - p);
14670     buf[q - p] = NULLCHAR;
14671     if (!local) {
14672         strcat(buf, "@");
14673         strcat(buf, host);
14674     }
14675 }
14676
14677 char *
14678 TimeControlTagValue ()
14679 {
14680     char buf[MSG_SIZ];
14681     if (!appData.clockMode) {
14682       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14683     } else if (movesPerSession > 0) {
14684       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14685     } else if (timeIncrement == 0) {
14686       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14687     } else {
14688       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14689     }
14690     return StrSave(buf);
14691 }
14692
14693 void
14694 SetGameInfo ()
14695 {
14696     /* This routine is used only for certain modes */
14697     VariantClass v = gameInfo.variant;
14698     ChessMove r = GameUnfinished;
14699     char *p = NULL;
14700
14701     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14702         r = gameInfo.result;
14703         p = gameInfo.resultDetails;
14704         gameInfo.resultDetails = NULL;
14705     }
14706     ClearGameInfo(&gameInfo);
14707     gameInfo.variant = v;
14708
14709     switch (gameMode) {
14710       case MachinePlaysWhite:
14711         gameInfo.event = StrSave( appData.pgnEventHeader );
14712         gameInfo.site = StrSave(HostName());
14713         gameInfo.date = PGNDate();
14714         gameInfo.round = StrSave("-");
14715         gameInfo.white = StrSave(first.tidy);
14716         gameInfo.black = StrSave(UserName());
14717         gameInfo.timeControl = TimeControlTagValue();
14718         break;
14719
14720       case MachinePlaysBlack:
14721         gameInfo.event = StrSave( appData.pgnEventHeader );
14722         gameInfo.site = StrSave(HostName());
14723         gameInfo.date = PGNDate();
14724         gameInfo.round = StrSave("-");
14725         gameInfo.white = StrSave(UserName());
14726         gameInfo.black = StrSave(first.tidy);
14727         gameInfo.timeControl = TimeControlTagValue();
14728         break;
14729
14730       case TwoMachinesPlay:
14731         gameInfo.event = StrSave( appData.pgnEventHeader );
14732         gameInfo.site = StrSave(HostName());
14733         gameInfo.date = PGNDate();
14734         if (roundNr > 0) {
14735             char buf[MSG_SIZ];
14736             snprintf(buf, MSG_SIZ, "%d", roundNr);
14737             gameInfo.round = StrSave(buf);
14738         } else {
14739             gameInfo.round = StrSave("-");
14740         }
14741         if (first.twoMachinesColor[0] == 'w') {
14742             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14743             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14744         } else {
14745             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14746             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14747         }
14748         gameInfo.timeControl = TimeControlTagValue();
14749         break;
14750
14751       case EditGame:
14752         gameInfo.event = StrSave("Edited game");
14753         gameInfo.site = StrSave(HostName());
14754         gameInfo.date = PGNDate();
14755         gameInfo.round = StrSave("-");
14756         gameInfo.white = StrSave("-");
14757         gameInfo.black = StrSave("-");
14758         gameInfo.result = r;
14759         gameInfo.resultDetails = p;
14760         break;
14761
14762       case EditPosition:
14763         gameInfo.event = StrSave("Edited position");
14764         gameInfo.site = StrSave(HostName());
14765         gameInfo.date = PGNDate();
14766         gameInfo.round = StrSave("-");
14767         gameInfo.white = StrSave("-");
14768         gameInfo.black = StrSave("-");
14769         break;
14770
14771       case IcsPlayingWhite:
14772       case IcsPlayingBlack:
14773       case IcsObserving:
14774       case IcsExamining:
14775         break;
14776
14777       case PlayFromGameFile:
14778         gameInfo.event = StrSave("Game from non-PGN file");
14779         gameInfo.site = StrSave(HostName());
14780         gameInfo.date = PGNDate();
14781         gameInfo.round = StrSave("-");
14782         gameInfo.white = StrSave("?");
14783         gameInfo.black = StrSave("?");
14784         break;
14785
14786       default:
14787         break;
14788     }
14789 }
14790
14791 void
14792 ReplaceComment (int index, char *text)
14793 {
14794     int len;
14795     char *p;
14796     float score;
14797
14798     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14799        pvInfoList[index-1].depth == len &&
14800        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14801        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14802     while (*text == '\n') text++;
14803     len = strlen(text);
14804     while (len > 0 && text[len - 1] == '\n') len--;
14805
14806     if (commentList[index] != NULL)
14807       free(commentList[index]);
14808
14809     if (len == 0) {
14810         commentList[index] = NULL;
14811         return;
14812     }
14813   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14814       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14815       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14816     commentList[index] = (char *) malloc(len + 2);
14817     strncpy(commentList[index], text, len);
14818     commentList[index][len] = '\n';
14819     commentList[index][len + 1] = NULLCHAR;
14820   } else {
14821     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14822     char *p;
14823     commentList[index] = (char *) malloc(len + 7);
14824     safeStrCpy(commentList[index], "{\n", 3);
14825     safeStrCpy(commentList[index]+2, text, len+1);
14826     commentList[index][len+2] = NULLCHAR;
14827     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14828     strcat(commentList[index], "\n}\n");
14829   }
14830 }
14831
14832 void
14833 CrushCRs (char *text)
14834 {
14835   char *p = text;
14836   char *q = text;
14837   char ch;
14838
14839   do {
14840     ch = *p++;
14841     if (ch == '\r') continue;
14842     *q++ = ch;
14843   } while (ch != '\0');
14844 }
14845
14846 void
14847 AppendComment (int index, char *text, Boolean addBraces)
14848 /* addBraces  tells if we should add {} */
14849 {
14850     int oldlen, len;
14851     char *old;
14852
14853 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14854     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14855
14856     CrushCRs(text);
14857     while (*text == '\n') text++;
14858     len = strlen(text);
14859     while (len > 0 && text[len - 1] == '\n') len--;
14860     text[len] = NULLCHAR;
14861
14862     if (len == 0) return;
14863
14864     if (commentList[index] != NULL) {
14865       Boolean addClosingBrace = addBraces;
14866         old = commentList[index];
14867         oldlen = strlen(old);
14868         while(commentList[index][oldlen-1] ==  '\n')
14869           commentList[index][--oldlen] = NULLCHAR;
14870         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14871         safeStrCpy(commentList[index], old, oldlen + len + 6);
14872         free(old);
14873         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14874         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14875           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14876           while (*text == '\n') { text++; len--; }
14877           commentList[index][--oldlen] = NULLCHAR;
14878       }
14879         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14880         else          strcat(commentList[index], "\n");
14881         strcat(commentList[index], text);
14882         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14883         else          strcat(commentList[index], "\n");
14884     } else {
14885         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14886         if(addBraces)
14887           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14888         else commentList[index][0] = NULLCHAR;
14889         strcat(commentList[index], text);
14890         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14891         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14892     }
14893 }
14894
14895 static char *
14896 FindStr (char * text, char * sub_text)
14897 {
14898     char * result = strstr( text, sub_text );
14899
14900     if( result != NULL ) {
14901         result += strlen( sub_text );
14902     }
14903
14904     return result;
14905 }
14906
14907 /* [AS] Try to extract PV info from PGN comment */
14908 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14909 char *
14910 GetInfoFromComment (int index, char * text)
14911 {
14912     char * sep = text, *p;
14913
14914     if( text != NULL && index > 0 ) {
14915         int score = 0;
14916         int depth = 0;
14917         int time = -1, sec = 0, deci;
14918         char * s_eval = FindStr( text, "[%eval " );
14919         char * s_emt = FindStr( text, "[%emt " );
14920
14921         if( s_eval != NULL || s_emt != NULL ) {
14922             /* New style */
14923             char delim;
14924
14925             if( s_eval != NULL ) {
14926                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14927                     return text;
14928                 }
14929
14930                 if( delim != ']' ) {
14931                     return text;
14932                 }
14933             }
14934
14935             if( s_emt != NULL ) {
14936             }
14937                 return text;
14938         }
14939         else {
14940             /* We expect something like: [+|-]nnn.nn/dd */
14941             int score_lo = 0;
14942
14943             if(*text != '{') return text; // [HGM] braces: must be normal comment
14944
14945             sep = strchr( text, '/' );
14946             if( sep == NULL || sep < (text+4) ) {
14947                 return text;
14948             }
14949
14950             p = text;
14951             if(p[1] == '(') { // comment starts with PV
14952                p = strchr(p, ')'); // locate end of PV
14953                if(p == NULL || sep < p+5) return text;
14954                // at this point we have something like "{(.*) +0.23/6 ..."
14955                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14956                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14957                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14958             }
14959             time = -1; sec = -1; deci = -1;
14960             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14961                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14962                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14963                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14964                 return text;
14965             }
14966
14967             if( score_lo < 0 || score_lo >= 100 ) {
14968                 return text;
14969             }
14970
14971             if(sec >= 0) time = 600*time + 10*sec; else
14972             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14973
14974             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14975
14976             /* [HGM] PV time: now locate end of PV info */
14977             while( *++sep >= '0' && *sep <= '9'); // strip depth
14978             if(time >= 0)
14979             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14980             if(sec >= 0)
14981             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14982             if(deci >= 0)
14983             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14984             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
14985         }
14986
14987         if( depth <= 0 ) {
14988             return text;
14989         }
14990
14991         if( time < 0 ) {
14992             time = -1;
14993         }
14994
14995         pvInfoList[index-1].depth = depth;
14996         pvInfoList[index-1].score = score;
14997         pvInfoList[index-1].time  = 10*time; // centi-sec
14998         if(*sep == '}') *sep = 0; else *--sep = '{';
14999         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15000     }
15001     return sep;
15002 }
15003
15004 void
15005 SendToProgram (char *message, ChessProgramState *cps)
15006 {
15007     int count, outCount, error;
15008     char buf[MSG_SIZ];
15009
15010     if (cps->pr == NoProc) return;
15011     Attention(cps);
15012
15013     if (appData.debugMode) {
15014         TimeMark now;
15015         GetTimeMark(&now);
15016         fprintf(debugFP, "%ld >%-6s: %s",
15017                 SubtractTimeMarks(&now, &programStartTime),
15018                 cps->which, message);
15019     }
15020
15021     count = strlen(message);
15022     outCount = OutputToProcess(cps->pr, message, count, &error);
15023     if (outCount < count && !exiting
15024                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15025       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15026       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15027         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15028             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15029                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15030                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15031                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15032             } else {
15033                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15034                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15035                 gameInfo.result = res;
15036             }
15037             gameInfo.resultDetails = StrSave(buf);
15038         }
15039         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15040         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15041     }
15042 }
15043
15044 void
15045 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15046 {
15047     char *end_str;
15048     char buf[MSG_SIZ];
15049     ChessProgramState *cps = (ChessProgramState *)closure;
15050
15051     if (isr != cps->isr) return; /* Killed intentionally */
15052     if (count <= 0) {
15053         if (count == 0) {
15054             RemoveInputSource(cps->isr);
15055             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15056             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15057                     _(cps->which), cps->program);
15058         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15059                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15060                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15061                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15062                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15063                 } else {
15064                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15065                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15066                     gameInfo.result = res;
15067                 }
15068                 gameInfo.resultDetails = StrSave(buf);
15069             }
15070             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15071             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15072         } else {
15073             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15074                     _(cps->which), cps->program);
15075             RemoveInputSource(cps->isr);
15076
15077             /* [AS] Program is misbehaving badly... kill it */
15078             if( count == -2 ) {
15079                 DestroyChildProcess( cps->pr, 9 );
15080                 cps->pr = NoProc;
15081             }
15082
15083             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15084         }
15085         return;
15086     }
15087
15088     if ((end_str = strchr(message, '\r')) != NULL)
15089       *end_str = NULLCHAR;
15090     if ((end_str = strchr(message, '\n')) != NULL)
15091       *end_str = NULLCHAR;
15092
15093     if (appData.debugMode) {
15094         TimeMark now; int print = 1;
15095         char *quote = ""; char c; int i;
15096
15097         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15098                 char start = message[0];
15099                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15100                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15101                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15102                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15103                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15104                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15105                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15106                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15107                    sscanf(message, "hint: %c", &c)!=1 && 
15108                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15109                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15110                     print = (appData.engineComments >= 2);
15111                 }
15112                 message[0] = start; // restore original message
15113         }
15114         if(print) {
15115                 GetTimeMark(&now);
15116                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15117                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15118                         quote,
15119                         message);
15120         }
15121     }
15122
15123     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15124     if (appData.icsEngineAnalyze) {
15125         if (strstr(message, "whisper") != NULL ||
15126              strstr(message, "kibitz") != NULL ||
15127             strstr(message, "tellics") != NULL) return;
15128     }
15129
15130     HandleMachineMove(message, cps);
15131 }
15132
15133
15134 void
15135 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15136 {
15137     char buf[MSG_SIZ];
15138     int seconds;
15139
15140     if( timeControl_2 > 0 ) {
15141         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15142             tc = timeControl_2;
15143         }
15144     }
15145     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15146     inc /= cps->timeOdds;
15147     st  /= cps->timeOdds;
15148
15149     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15150
15151     if (st > 0) {
15152       /* Set exact time per move, normally using st command */
15153       if (cps->stKludge) {
15154         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15155         seconds = st % 60;
15156         if (seconds == 0) {
15157           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15158         } else {
15159           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15160         }
15161       } else {
15162         snprintf(buf, MSG_SIZ, "st %d\n", st);
15163       }
15164     } else {
15165       /* Set conventional or incremental time control, using level command */
15166       if (seconds == 0) {
15167         /* Note old gnuchess bug -- minutes:seconds used to not work.
15168            Fixed in later versions, but still avoid :seconds
15169            when seconds is 0. */
15170         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15171       } else {
15172         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15173                  seconds, inc/1000.);
15174       }
15175     }
15176     SendToProgram(buf, cps);
15177
15178     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15179     /* Orthogonally, limit search to given depth */
15180     if (sd > 0) {
15181       if (cps->sdKludge) {
15182         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15183       } else {
15184         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15185       }
15186       SendToProgram(buf, cps);
15187     }
15188
15189     if(cps->nps >= 0) { /* [HGM] nps */
15190         if(cps->supportsNPS == FALSE)
15191           cps->nps = -1; // don't use if engine explicitly says not supported!
15192         else {
15193           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15194           SendToProgram(buf, cps);
15195         }
15196     }
15197 }
15198
15199 ChessProgramState *
15200 WhitePlayer ()
15201 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15202 {
15203     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15204        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15205         return &second;
15206     return &first;
15207 }
15208
15209 void
15210 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15211 {
15212     char message[MSG_SIZ];
15213     long time, otime;
15214
15215     /* Note: this routine must be called when the clocks are stopped
15216        or when they have *just* been set or switched; otherwise
15217        it will be off by the time since the current tick started.
15218     */
15219     if (machineWhite) {
15220         time = whiteTimeRemaining / 10;
15221         otime = blackTimeRemaining / 10;
15222     } else {
15223         time = blackTimeRemaining / 10;
15224         otime = whiteTimeRemaining / 10;
15225     }
15226     /* [HGM] translate opponent's time by time-odds factor */
15227     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15228     if (appData.debugMode) {
15229         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15230     }
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
15613       if (gameMode != EditGame && currentMove != forwardMostMove && 
15614         gameMode != Training) {
15615         DisplayMoveError(_("Displayed move is not current"));
15616       } else {
15617         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15618           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15619         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15620         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15621           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15622           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15623         } else {
15624           DisplayMoveError(_("Could not parse move"));
15625         }
15626       }
15627 }
15628
15629 void
15630 DisplayMove (int moveNumber)
15631 {
15632     char message[MSG_SIZ];
15633     char res[MSG_SIZ];
15634     char cpThinkOutput[MSG_SIZ];
15635
15636     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15637
15638     if (moveNumber == forwardMostMove - 1 ||
15639         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15640
15641         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15642
15643         if (strchr(cpThinkOutput, '\n')) {
15644             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15645         }
15646     } else {
15647         *cpThinkOutput = NULLCHAR;
15648     }
15649
15650     /* [AS] Hide thinking from human user */
15651     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15652         *cpThinkOutput = NULLCHAR;
15653         if( thinkOutput[0] != NULLCHAR ) {
15654             int i;
15655
15656             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15657                 cpThinkOutput[i] = '.';
15658             }
15659             cpThinkOutput[i] = NULLCHAR;
15660             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15661         }
15662     }
15663
15664     if (moveNumber == forwardMostMove - 1 &&
15665         gameInfo.resultDetails != NULL) {
15666         if (gameInfo.resultDetails[0] == NULLCHAR) {
15667           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15668         } else {
15669           snprintf(res, MSG_SIZ, " {%s} %s",
15670                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15671         }
15672     } else {
15673         res[0] = NULLCHAR;
15674     }
15675
15676     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15677         DisplayMessage(res, cpThinkOutput);
15678     } else {
15679       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15680                 WhiteOnMove(moveNumber) ? " " : ".. ",
15681                 parseList[moveNumber], res);
15682         DisplayMessage(message, cpThinkOutput);
15683     }
15684 }
15685
15686 void
15687 DisplayComment (int moveNumber, char *text)
15688 {
15689     char title[MSG_SIZ];
15690
15691     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15692       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15693     } else {
15694       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15695               WhiteOnMove(moveNumber) ? " " : ".. ",
15696               parseList[moveNumber]);
15697     }
15698     if (text != NULL && (appData.autoDisplayComment || commentUp))
15699         CommentPopUp(title, text);
15700 }
15701
15702 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15703  * might be busy thinking or pondering.  It can be omitted if your
15704  * gnuchess is configured to stop thinking immediately on any user
15705  * input.  However, that gnuchess feature depends on the FIONREAD
15706  * ioctl, which does not work properly on some flavors of Unix.
15707  */
15708 void
15709 Attention (ChessProgramState *cps)
15710 {
15711 #if ATTENTION
15712     if (!cps->useSigint) return;
15713     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15714     switch (gameMode) {
15715       case MachinePlaysWhite:
15716       case MachinePlaysBlack:
15717       case TwoMachinesPlay:
15718       case IcsPlayingWhite:
15719       case IcsPlayingBlack:
15720       case AnalyzeMode:
15721       case AnalyzeFile:
15722         /* Skip if we know it isn't thinking */
15723         if (!cps->maybeThinking) return;
15724         if (appData.debugMode)
15725           fprintf(debugFP, "Interrupting %s\n", cps->which);
15726         InterruptChildProcess(cps->pr);
15727         cps->maybeThinking = FALSE;
15728         break;
15729       default:
15730         break;
15731     }
15732 #endif /*ATTENTION*/
15733 }
15734
15735 int
15736 CheckFlags ()
15737 {
15738     if (whiteTimeRemaining <= 0) {
15739         if (!whiteFlag) {
15740             whiteFlag = TRUE;
15741             if (appData.icsActive) {
15742                 if (appData.autoCallFlag &&
15743                     gameMode == IcsPlayingBlack && !blackFlag) {
15744                   SendToICS(ics_prefix);
15745                   SendToICS("flag\n");
15746                 }
15747             } else {
15748                 if (blackFlag) {
15749                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15750                 } else {
15751                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15752                     if (appData.autoCallFlag) {
15753                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15754                         return TRUE;
15755                     }
15756                 }
15757             }
15758         }
15759     }
15760     if (blackTimeRemaining <= 0) {
15761         if (!blackFlag) {
15762             blackFlag = TRUE;
15763             if (appData.icsActive) {
15764                 if (appData.autoCallFlag &&
15765                     gameMode == IcsPlayingWhite && !whiteFlag) {
15766                   SendToICS(ics_prefix);
15767                   SendToICS("flag\n");
15768                 }
15769             } else {
15770                 if (whiteFlag) {
15771                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15772                 } else {
15773                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15774                     if (appData.autoCallFlag) {
15775                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15776                         return TRUE;
15777                     }
15778                 }
15779             }
15780         }
15781     }
15782     return FALSE;
15783 }
15784
15785 void
15786 CheckTimeControl ()
15787 {
15788     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15789         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15790
15791     /*
15792      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15793      */
15794     if ( !WhiteOnMove(forwardMostMove) ) {
15795         /* White made time control */
15796         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15797         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15798         /* [HGM] time odds: correct new time quota for time odds! */
15799                                             / WhitePlayer()->timeOdds;
15800         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15801     } else {
15802         lastBlack -= blackTimeRemaining;
15803         /* Black made time control */
15804         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15805                                             / WhitePlayer()->other->timeOdds;
15806         lastWhite = whiteTimeRemaining;
15807     }
15808 }
15809
15810 void
15811 DisplayBothClocks ()
15812 {
15813     int wom = gameMode == EditPosition ?
15814       !blackPlaysFirst : WhiteOnMove(currentMove);
15815     DisplayWhiteClock(whiteTimeRemaining, wom);
15816     DisplayBlackClock(blackTimeRemaining, !wom);
15817 }
15818
15819
15820 /* Timekeeping seems to be a portability nightmare.  I think everyone
15821    has ftime(), but I'm really not sure, so I'm including some ifdefs
15822    to use other calls if you don't.  Clocks will be less accurate if
15823    you have neither ftime nor gettimeofday.
15824 */
15825
15826 /* VS 2008 requires the #include outside of the function */
15827 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15828 #include <sys/timeb.h>
15829 #endif
15830
15831 /* Get the current time as a TimeMark */
15832 void
15833 GetTimeMark (TimeMark *tm)
15834 {
15835 #if HAVE_GETTIMEOFDAY
15836
15837     struct timeval timeVal;
15838     struct timezone timeZone;
15839
15840     gettimeofday(&timeVal, &timeZone);
15841     tm->sec = (long) timeVal.tv_sec;
15842     tm->ms = (int) (timeVal.tv_usec / 1000L);
15843
15844 #else /*!HAVE_GETTIMEOFDAY*/
15845 #if HAVE_FTIME
15846
15847 // include <sys/timeb.h> / moved to just above start of function
15848     struct timeb timeB;
15849
15850     ftime(&timeB);
15851     tm->sec = (long) timeB.time;
15852     tm->ms = (int) timeB.millitm;
15853
15854 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15855     tm->sec = (long) time(NULL);
15856     tm->ms = 0;
15857 #endif
15858 #endif
15859 }
15860
15861 /* Return the difference in milliseconds between two
15862    time marks.  We assume the difference will fit in a long!
15863 */
15864 long
15865 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
15866 {
15867     return 1000L*(tm2->sec - tm1->sec) +
15868            (long) (tm2->ms - tm1->ms);
15869 }
15870
15871
15872 /*
15873  * Code to manage the game clocks.
15874  *
15875  * In tournament play, black starts the clock and then white makes a move.
15876  * We give the human user a slight advantage if he is playing white---the
15877  * clocks don't run until he makes his first move, so it takes zero time.
15878  * Also, we don't account for network lag, so we could get out of sync
15879  * with GNU Chess's clock -- but then, referees are always right.
15880  */
15881
15882 static TimeMark tickStartTM;
15883 static long intendedTickLength;
15884
15885 long
15886 NextTickLength (long timeRemaining)
15887 {
15888     long nominalTickLength, nextTickLength;
15889
15890     if (timeRemaining > 0L && timeRemaining <= 10000L)
15891       nominalTickLength = 100L;
15892     else
15893       nominalTickLength = 1000L;
15894     nextTickLength = timeRemaining % nominalTickLength;
15895     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15896
15897     return nextTickLength;
15898 }
15899
15900 /* Adjust clock one minute up or down */
15901 void
15902 AdjustClock (Boolean which, int dir)
15903 {
15904     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15905     if(which) blackTimeRemaining += 60000*dir;
15906     else      whiteTimeRemaining += 60000*dir;
15907     DisplayBothClocks();
15908     adjustedClock = TRUE;
15909 }
15910
15911 /* Stop clocks and reset to a fresh time control */
15912 void
15913 ResetClocks ()
15914 {
15915     (void) StopClockTimer();
15916     if (appData.icsActive) {
15917         whiteTimeRemaining = blackTimeRemaining = 0;
15918     } else if (searchTime) {
15919         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15920         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15921     } else { /* [HGM] correct new time quote for time odds */
15922         whiteTC = blackTC = fullTimeControlString;
15923         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15924         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15925     }
15926     if (whiteFlag || blackFlag) {
15927         DisplayTitle("");
15928         whiteFlag = blackFlag = FALSE;
15929     }
15930     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15931     DisplayBothClocks();
15932     adjustedClock = FALSE;
15933 }
15934
15935 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15936
15937 /* Decrement running clock by amount of time that has passed */
15938 void
15939 DecrementClocks ()
15940 {
15941     long timeRemaining;
15942     long lastTickLength, fudge;
15943     TimeMark now;
15944
15945     if (!appData.clockMode) return;
15946     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15947
15948     GetTimeMark(&now);
15949
15950     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15951
15952     /* Fudge if we woke up a little too soon */
15953     fudge = intendedTickLength - lastTickLength;
15954     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15955
15956     if (WhiteOnMove(forwardMostMove)) {
15957         if(whiteNPS >= 0) lastTickLength = 0;
15958         timeRemaining = whiteTimeRemaining -= lastTickLength;
15959         if(timeRemaining < 0 && !appData.icsActive) {
15960             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15961             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15962                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15963                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15964             }
15965         }
15966         DisplayWhiteClock(whiteTimeRemaining - fudge,
15967                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15968     } else {
15969         if(blackNPS >= 0) lastTickLength = 0;
15970         timeRemaining = blackTimeRemaining -= lastTickLength;
15971         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15972             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15973             if(suddenDeath) {
15974                 blackStartMove = forwardMostMove;
15975                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15976             }
15977         }
15978         DisplayBlackClock(blackTimeRemaining - fudge,
15979                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15980     }
15981     if (CheckFlags()) return;
15982
15983     tickStartTM = now;
15984     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15985     StartClockTimer(intendedTickLength);
15986
15987     /* if the time remaining has fallen below the alarm threshold, sound the
15988      * alarm. if the alarm has sounded and (due to a takeback or time control
15989      * with increment) the time remaining has increased to a level above the
15990      * threshold, reset the alarm so it can sound again.
15991      */
15992
15993     if (appData.icsActive && appData.icsAlarm) {
15994
15995         /* make sure we are dealing with the user's clock */
15996         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15997                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15998            )) return;
15999
16000         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16001             alarmSounded = FALSE;
16002         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16003             PlayAlarmSound();
16004             alarmSounded = TRUE;
16005         }
16006     }
16007 }
16008
16009
16010 /* A player has just moved, so stop the previously running
16011    clock and (if in clock mode) start the other one.
16012    We redisplay both clocks in case we're in ICS mode, because
16013    ICS gives us an update to both clocks after every move.
16014    Note that this routine is called *after* forwardMostMove
16015    is updated, so the last fractional tick must be subtracted
16016    from the color that is *not* on move now.
16017 */
16018 void
16019 SwitchClocks (int newMoveNr)
16020 {
16021     long lastTickLength;
16022     TimeMark now;
16023     int flagged = FALSE;
16024
16025     GetTimeMark(&now);
16026
16027     if (StopClockTimer() && appData.clockMode) {
16028         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16029         if (!WhiteOnMove(forwardMostMove)) {
16030             if(blackNPS >= 0) lastTickLength = 0;
16031             blackTimeRemaining -= lastTickLength;
16032            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16033 //         if(pvInfoList[forwardMostMove].time == -1)
16034                  pvInfoList[forwardMostMove].time =               // use GUI time
16035                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16036         } else {
16037            if(whiteNPS >= 0) lastTickLength = 0;
16038            whiteTimeRemaining -= lastTickLength;
16039            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16040 //         if(pvInfoList[forwardMostMove].time == -1)
16041                  pvInfoList[forwardMostMove].time =
16042                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16043         }
16044         flagged = CheckFlags();
16045     }
16046     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16047     CheckTimeControl();
16048
16049     if (flagged || !appData.clockMode) return;
16050
16051     switch (gameMode) {
16052       case MachinePlaysBlack:
16053       case MachinePlaysWhite:
16054       case BeginningOfGame:
16055         if (pausing) return;
16056         break;
16057
16058       case EditGame:
16059       case PlayFromGameFile:
16060       case IcsExamining:
16061         return;
16062
16063       default:
16064         break;
16065     }
16066
16067     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16068         if(WhiteOnMove(forwardMostMove))
16069              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16070         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16071     }
16072
16073     tickStartTM = now;
16074     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16075       whiteTimeRemaining : blackTimeRemaining);
16076     StartClockTimer(intendedTickLength);
16077 }
16078
16079
16080 /* Stop both clocks */
16081 void
16082 StopClocks ()
16083 {
16084     long lastTickLength;
16085     TimeMark now;
16086
16087     if (!StopClockTimer()) return;
16088     if (!appData.clockMode) return;
16089
16090     GetTimeMark(&now);
16091
16092     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16093     if (WhiteOnMove(forwardMostMove)) {
16094         if(whiteNPS >= 0) lastTickLength = 0;
16095         whiteTimeRemaining -= lastTickLength;
16096         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16097     } else {
16098         if(blackNPS >= 0) lastTickLength = 0;
16099         blackTimeRemaining -= lastTickLength;
16100         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16101     }
16102     CheckFlags();
16103 }
16104
16105 /* Start clock of player on move.  Time may have been reset, so
16106    if clock is already running, stop and restart it. */
16107 void
16108 StartClocks ()
16109 {
16110     (void) StopClockTimer(); /* in case it was running already */
16111     DisplayBothClocks();
16112     if (CheckFlags()) return;
16113
16114     if (!appData.clockMode) return;
16115     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16116
16117     GetTimeMark(&tickStartTM);
16118     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16119       whiteTimeRemaining : blackTimeRemaining);
16120
16121    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16122     whiteNPS = blackNPS = -1;
16123     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16124        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16125         whiteNPS = first.nps;
16126     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16127        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16128         blackNPS = first.nps;
16129     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16130         whiteNPS = second.nps;
16131     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16132         blackNPS = second.nps;
16133     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16134
16135     StartClockTimer(intendedTickLength);
16136 }
16137
16138 char *
16139 TimeString (long ms)
16140 {
16141     long second, minute, hour, day;
16142     char *sign = "";
16143     static char buf[32];
16144
16145     if (ms > 0 && ms <= 9900) {
16146       /* convert milliseconds to tenths, rounding up */
16147       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16148
16149       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16150       return buf;
16151     }
16152
16153     /* convert milliseconds to seconds, rounding up */
16154     /* use floating point to avoid strangeness of integer division
16155        with negative dividends on many machines */
16156     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16157
16158     if (second < 0) {
16159         sign = "-";
16160         second = -second;
16161     }
16162
16163     day = second / (60 * 60 * 24);
16164     second = second % (60 * 60 * 24);
16165     hour = second / (60 * 60);
16166     second = second % (60 * 60);
16167     minute = second / 60;
16168     second = second % 60;
16169
16170     if (day > 0)
16171       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16172               sign, day, hour, minute, second);
16173     else if (hour > 0)
16174       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16175     else
16176       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16177
16178     return buf;
16179 }
16180
16181
16182 /*
16183  * This is necessary because some C libraries aren't ANSI C compliant yet.
16184  */
16185 char *
16186 StrStr (char *string, char *match)
16187 {
16188     int i, length;
16189
16190     length = strlen(match);
16191
16192     for (i = strlen(string) - length; i >= 0; i--, string++)
16193       if (!strncmp(match, string, length))
16194         return string;
16195
16196     return NULL;
16197 }
16198
16199 char *
16200 StrCaseStr (char *string, char *match)
16201 {
16202     int i, j, length;
16203
16204     length = strlen(match);
16205
16206     for (i = strlen(string) - length; i >= 0; i--, string++) {
16207         for (j = 0; j < length; j++) {
16208             if (ToLower(match[j]) != ToLower(string[j]))
16209               break;
16210         }
16211         if (j == length) return string;
16212     }
16213
16214     return NULL;
16215 }
16216
16217 #ifndef _amigados
16218 int
16219 StrCaseCmp (char *s1, char *s2)
16220 {
16221     char c1, c2;
16222
16223     for (;;) {
16224         c1 = ToLower(*s1++);
16225         c2 = ToLower(*s2++);
16226         if (c1 > c2) return 1;
16227         if (c1 < c2) return -1;
16228         if (c1 == NULLCHAR) return 0;
16229     }
16230 }
16231
16232
16233 int
16234 ToLower (int c)
16235 {
16236     return isupper(c) ? tolower(c) : c;
16237 }
16238
16239
16240 int
16241 ToUpper (int c)
16242 {
16243     return islower(c) ? toupper(c) : c;
16244 }
16245 #endif /* !_amigados    */
16246
16247 char *
16248 StrSave (char *s)
16249 {
16250   char *ret;
16251
16252   if ((ret = (char *) malloc(strlen(s) + 1)))
16253     {
16254       safeStrCpy(ret, s, strlen(s)+1);
16255     }
16256   return ret;
16257 }
16258
16259 char *
16260 StrSavePtr (char *s, char **savePtr)
16261 {
16262     if (*savePtr) {
16263         free(*savePtr);
16264     }
16265     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16266       safeStrCpy(*savePtr, s, strlen(s)+1);
16267     }
16268     return(*savePtr);
16269 }
16270
16271 char *
16272 PGNDate ()
16273 {
16274     time_t clock;
16275     struct tm *tm;
16276     char buf[MSG_SIZ];
16277
16278     clock = time((time_t *)NULL);
16279     tm = localtime(&clock);
16280     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16281             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16282     return StrSave(buf);
16283 }
16284
16285
16286 char *
16287 PositionToFEN (int move, char *overrideCastling)
16288 {
16289     int i, j, fromX, fromY, toX, toY;
16290     int whiteToPlay;
16291     char buf[MSG_SIZ];
16292     char *p, *q;
16293     int emptycount;
16294     ChessSquare piece;
16295
16296     whiteToPlay = (gameMode == EditPosition) ?
16297       !blackPlaysFirst : (move % 2 == 0);
16298     p = buf;
16299
16300     /* Piece placement data */
16301     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16302         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16303         emptycount = 0;
16304         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16305             if (boards[move][i][j] == EmptySquare) {
16306                 emptycount++;
16307             } else { ChessSquare piece = boards[move][i][j];
16308                 if (emptycount > 0) {
16309                     if(emptycount<10) /* [HGM] can be >= 10 */
16310                         *p++ = '0' + emptycount;
16311                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16312                     emptycount = 0;
16313                 }
16314                 if(PieceToChar(piece) == '+') {
16315                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16316                     *p++ = '+';
16317                     piece = (ChessSquare)(DEMOTED piece);
16318                 }
16319                 *p++ = PieceToChar(piece);
16320                 if(p[-1] == '~') {
16321                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16322                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16323                     *p++ = '~';
16324                 }
16325             }
16326         }
16327         if (emptycount > 0) {
16328             if(emptycount<10) /* [HGM] can be >= 10 */
16329                 *p++ = '0' + emptycount;
16330             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16331             emptycount = 0;
16332         }
16333         *p++ = '/';
16334     }
16335     *(p - 1) = ' ';
16336
16337     /* [HGM] print Crazyhouse or Shogi holdings */
16338     if( gameInfo.holdingsWidth ) {
16339         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16340         q = p;
16341         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16342             piece = boards[move][i][BOARD_WIDTH-1];
16343             if( piece != EmptySquare )
16344               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16345                   *p++ = PieceToChar(piece);
16346         }
16347         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16348             piece = boards[move][BOARD_HEIGHT-i-1][0];
16349             if( piece != EmptySquare )
16350               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16351                   *p++ = PieceToChar(piece);
16352         }
16353
16354         if( q == p ) *p++ = '-';
16355         *p++ = ']';
16356         *p++ = ' ';
16357     }
16358
16359     /* Active color */
16360     *p++ = whiteToPlay ? 'w' : 'b';
16361     *p++ = ' ';
16362
16363   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16364     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16365   } else {
16366   if(nrCastlingRights) {
16367      q = p;
16368      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16369        /* [HGM] write directly from rights */
16370            if(boards[move][CASTLING][2] != NoRights &&
16371               boards[move][CASTLING][0] != NoRights   )
16372                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16373            if(boards[move][CASTLING][2] != NoRights &&
16374               boards[move][CASTLING][1] != NoRights   )
16375                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16376            if(boards[move][CASTLING][5] != NoRights &&
16377               boards[move][CASTLING][3] != NoRights   )
16378                 *p++ = boards[move][CASTLING][3] + AAA;
16379            if(boards[move][CASTLING][5] != NoRights &&
16380               boards[move][CASTLING][4] != NoRights   )
16381                 *p++ = boards[move][CASTLING][4] + AAA;
16382      } else {
16383
16384         /* [HGM] write true castling rights */
16385         if( nrCastlingRights == 6 ) {
16386             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16387                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16388             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16389                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16390             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16391                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16392             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16393                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16394         }
16395      }
16396      if (q == p) *p++ = '-'; /* No castling rights */
16397      *p++ = ' ';
16398   }
16399
16400   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16401      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16402     /* En passant target square */
16403     if (move > backwardMostMove) {
16404         fromX = moveList[move - 1][0] - AAA;
16405         fromY = moveList[move - 1][1] - ONE;
16406         toX = moveList[move - 1][2] - AAA;
16407         toY = moveList[move - 1][3] - ONE;
16408         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16409             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16410             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16411             fromX == toX) {
16412             /* 2-square pawn move just happened */
16413             *p++ = toX + AAA;
16414             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16415         } else {
16416             *p++ = '-';
16417         }
16418     } else if(move == backwardMostMove) {
16419         // [HGM] perhaps we should always do it like this, and forget the above?
16420         if((signed char)boards[move][EP_STATUS] >= 0) {
16421             *p++ = boards[move][EP_STATUS] + AAA;
16422             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16423         } else {
16424             *p++ = '-';
16425         }
16426     } else {
16427         *p++ = '-';
16428     }
16429     *p++ = ' ';
16430   }
16431   }
16432
16433     /* [HGM] find reversible plies */
16434     {   int i = 0, j=move;
16435
16436         if (appData.debugMode) { int k;
16437             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16438             for(k=backwardMostMove; k<=forwardMostMove; k++)
16439                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16440
16441         }
16442
16443         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16444         if( j == backwardMostMove ) i += initialRulePlies;
16445         sprintf(p, "%d ", i);
16446         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16447     }
16448     /* Fullmove number */
16449     sprintf(p, "%d", (move / 2) + 1);
16450
16451     return StrSave(buf);
16452 }
16453
16454 Boolean
16455 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16456 {
16457     int i, j;
16458     char *p, c;
16459     int emptycount;
16460     ChessSquare piece;
16461
16462     p = fen;
16463
16464     /* [HGM] by default clear Crazyhouse holdings, if present */
16465     if(gameInfo.holdingsWidth) {
16466        for(i=0; i<BOARD_HEIGHT; i++) {
16467            board[i][0]             = EmptySquare; /* black holdings */
16468            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16469            board[i][1]             = (ChessSquare) 0; /* black counts */
16470            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16471        }
16472     }
16473
16474     /* Piece placement data */
16475     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16476         j = 0;
16477         for (;;) {
16478             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16479                 if (*p == '/') p++;
16480                 emptycount = gameInfo.boardWidth - j;
16481                 while (emptycount--)
16482                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16483                 break;
16484 #if(BOARD_FILES >= 10)
16485             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16486                 p++; emptycount=10;
16487                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16488                 while (emptycount--)
16489                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16490 #endif
16491             } else if (isdigit(*p)) {
16492                 emptycount = *p++ - '0';
16493                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16494                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16495                 while (emptycount--)
16496                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16497             } else if (*p == '+' || isalpha(*p)) {
16498                 if (j >= gameInfo.boardWidth) return FALSE;
16499                 if(*p=='+') {
16500                     piece = CharToPiece(*++p);
16501                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16502                     piece = (ChessSquare) (PROMOTED piece ); p++;
16503                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16504                 } else piece = CharToPiece(*p++);
16505
16506                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16507                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16508                     piece = (ChessSquare) (PROMOTED piece);
16509                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16510                     p++;
16511                 }
16512                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16513             } else {
16514                 return FALSE;
16515             }
16516         }
16517     }
16518     while (*p == '/' || *p == ' ') p++;
16519
16520     /* [HGM] look for Crazyhouse holdings here */
16521     while(*p==' ') p++;
16522     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16523         if(*p == '[') p++;
16524         if(*p == '-' ) p++; /* empty holdings */ else {
16525             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16526             /* if we would allow FEN reading to set board size, we would   */
16527             /* have to add holdings and shift the board read so far here   */
16528             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16529                 p++;
16530                 if((int) piece >= (int) BlackPawn ) {
16531                     i = (int)piece - (int)BlackPawn;
16532                     i = PieceToNumber((ChessSquare)i);
16533                     if( i >= gameInfo.holdingsSize ) return FALSE;
16534                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16535                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16536                 } else {
16537                     i = (int)piece - (int)WhitePawn;
16538                     i = PieceToNumber((ChessSquare)i);
16539                     if( i >= gameInfo.holdingsSize ) return FALSE;
16540                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16541                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16542                 }
16543             }
16544         }
16545         if(*p == ']') p++;
16546     }
16547
16548     while(*p == ' ') p++;
16549
16550     /* Active color */
16551     c = *p++;
16552     if(appData.colorNickNames) {
16553       if( c == appData.colorNickNames[0] ) c = 'w'; else
16554       if( c == appData.colorNickNames[1] ) c = 'b';
16555     }
16556     switch (c) {
16557       case 'w':
16558         *blackPlaysFirst = FALSE;
16559         break;
16560       case 'b':
16561         *blackPlaysFirst = TRUE;
16562         break;
16563       default:
16564         return FALSE;
16565     }
16566
16567     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16568     /* return the extra info in global variiables             */
16569
16570     /* set defaults in case FEN is incomplete */
16571     board[EP_STATUS] = EP_UNKNOWN;
16572     for(i=0; i<nrCastlingRights; i++ ) {
16573         board[CASTLING][i] =
16574             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16575     }   /* assume possible unless obviously impossible */
16576     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16577     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16578     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16579                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16580     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16581     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16582     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16583                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16584     FENrulePlies = 0;
16585
16586     while(*p==' ') p++;
16587     if(nrCastlingRights) {
16588       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16589           /* castling indicator present, so default becomes no castlings */
16590           for(i=0; i<nrCastlingRights; i++ ) {
16591                  board[CASTLING][i] = NoRights;
16592           }
16593       }
16594       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16595              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16596              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16597              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16598         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16599
16600         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16601             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16602             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16603         }
16604         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16605             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16606         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16607                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16608         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16609                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16610         switch(c) {
16611           case'K':
16612               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16613               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16614               board[CASTLING][2] = whiteKingFile;
16615               break;
16616           case'Q':
16617               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16618               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16619               board[CASTLING][2] = whiteKingFile;
16620               break;
16621           case'k':
16622               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16623               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16624               board[CASTLING][5] = blackKingFile;
16625               break;
16626           case'q':
16627               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16628               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16629               board[CASTLING][5] = blackKingFile;
16630           case '-':
16631               break;
16632           default: /* FRC castlings */
16633               if(c >= 'a') { /* black rights */
16634                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16635                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16636                   if(i == BOARD_RGHT) break;
16637                   board[CASTLING][5] = i;
16638                   c -= AAA;
16639                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16640                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16641                   if(c > i)
16642                       board[CASTLING][3] = c;
16643                   else
16644                       board[CASTLING][4] = c;
16645               } else { /* white rights */
16646                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16647                     if(board[0][i] == WhiteKing) break;
16648                   if(i == BOARD_RGHT) break;
16649                   board[CASTLING][2] = i;
16650                   c -= AAA - 'a' + 'A';
16651                   if(board[0][c] >= WhiteKing) break;
16652                   if(c > i)
16653                       board[CASTLING][0] = c;
16654                   else
16655                       board[CASTLING][1] = c;
16656               }
16657         }
16658       }
16659       for(i=0; i<nrCastlingRights; i++)
16660         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16661     if (appData.debugMode) {
16662         fprintf(debugFP, "FEN castling rights:");
16663         for(i=0; i<nrCastlingRights; i++)
16664         fprintf(debugFP, " %d", board[CASTLING][i]);
16665         fprintf(debugFP, "\n");
16666     }
16667
16668       while(*p==' ') p++;
16669     }
16670
16671     /* read e.p. field in games that know e.p. capture */
16672     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16673        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16674       if(*p=='-') {
16675         p++; board[EP_STATUS] = EP_NONE;
16676       } else {
16677          char c = *p++ - AAA;
16678
16679          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16680          if(*p >= '0' && *p <='9') p++;
16681          board[EP_STATUS] = c;
16682       }
16683     }
16684
16685
16686     if(sscanf(p, "%d", &i) == 1) {
16687         FENrulePlies = i; /* 50-move ply counter */
16688         /* (The move number is still ignored)    */
16689     }
16690
16691     return TRUE;
16692 }
16693
16694 void
16695 EditPositionPasteFEN (char *fen)
16696 {
16697   if (fen != NULL) {
16698     Board initial_position;
16699
16700     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16701       DisplayError(_("Bad FEN position in clipboard"), 0);
16702       return ;
16703     } else {
16704       int savedBlackPlaysFirst = blackPlaysFirst;
16705       EditPositionEvent();
16706       blackPlaysFirst = savedBlackPlaysFirst;
16707       CopyBoard(boards[0], initial_position);
16708       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16709       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16710       DisplayBothClocks();
16711       DrawPosition(FALSE, boards[currentMove]);
16712     }
16713   }
16714 }
16715
16716 static char cseq[12] = "\\   ";
16717
16718 Boolean
16719 set_cont_sequence (char *new_seq)
16720 {
16721     int len;
16722     Boolean ret;
16723
16724     // handle bad attempts to set the sequence
16725         if (!new_seq)
16726                 return 0; // acceptable error - no debug
16727
16728     len = strlen(new_seq);
16729     ret = (len > 0) && (len < sizeof(cseq));
16730     if (ret)
16731       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16732     else if (appData.debugMode)
16733       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16734     return ret;
16735 }
16736
16737 /*
16738     reformat a source message so words don't cross the width boundary.  internal
16739     newlines are not removed.  returns the wrapped size (no null character unless
16740     included in source message).  If dest is NULL, only calculate the size required
16741     for the dest buffer.  lp argument indicats line position upon entry, and it's
16742     passed back upon exit.
16743 */
16744 int
16745 wrap (char *dest, char *src, int count, int width, int *lp)
16746 {
16747     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16748
16749     cseq_len = strlen(cseq);
16750     old_line = line = *lp;
16751     ansi = len = clen = 0;
16752
16753     for (i=0; i < count; i++)
16754     {
16755         if (src[i] == '\033')
16756             ansi = 1;
16757
16758         // if we hit the width, back up
16759         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16760         {
16761             // store i & len in case the word is too long
16762             old_i = i, old_len = len;
16763
16764             // find the end of the last word
16765             while (i && src[i] != ' ' && src[i] != '\n')
16766             {
16767                 i--;
16768                 len--;
16769             }
16770
16771             // word too long?  restore i & len before splitting it
16772             if ((old_i-i+clen) >= width)
16773             {
16774                 i = old_i;
16775                 len = old_len;
16776             }
16777
16778             // extra space?
16779             if (i && src[i-1] == ' ')
16780                 len--;
16781
16782             if (src[i] != ' ' && src[i] != '\n')
16783             {
16784                 i--;
16785                 if (len)
16786                     len--;
16787             }
16788
16789             // now append the newline and continuation sequence
16790             if (dest)
16791                 dest[len] = '\n';
16792             len++;
16793             if (dest)
16794                 strncpy(dest+len, cseq, cseq_len);
16795             len += cseq_len;
16796             line = cseq_len;
16797             clen = cseq_len;
16798             continue;
16799         }
16800
16801         if (dest)
16802             dest[len] = src[i];
16803         len++;
16804         if (!ansi)
16805             line++;
16806         if (src[i] == '\n')
16807             line = 0;
16808         if (src[i] == 'm')
16809             ansi = 0;
16810     }
16811     if (dest && appData.debugMode)
16812     {
16813         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16814             count, width, line, len, *lp);
16815         show_bytes(debugFP, src, count);
16816         fprintf(debugFP, "\ndest: ");
16817         show_bytes(debugFP, dest, len);
16818         fprintf(debugFP, "\n");
16819     }
16820     *lp = dest ? line : old_line;
16821
16822     return len;
16823 }
16824
16825 // [HGM] vari: routines for shelving variations
16826 Boolean modeRestore = FALSE;
16827
16828 void
16829 PushInner (int firstMove, int lastMove)
16830 {
16831         int i, j, nrMoves = lastMove - firstMove;
16832
16833         // push current tail of game on stack
16834         savedResult[storedGames] = gameInfo.result;
16835         savedDetails[storedGames] = gameInfo.resultDetails;
16836         gameInfo.resultDetails = NULL;
16837         savedFirst[storedGames] = firstMove;
16838         savedLast [storedGames] = lastMove;
16839         savedFramePtr[storedGames] = framePtr;
16840         framePtr -= nrMoves; // reserve space for the boards
16841         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16842             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16843             for(j=0; j<MOVE_LEN; j++)
16844                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16845             for(j=0; j<2*MOVE_LEN; j++)
16846                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16847             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16848             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16849             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16850             pvInfoList[firstMove+i-1].depth = 0;
16851             commentList[framePtr+i] = commentList[firstMove+i];
16852             commentList[firstMove+i] = NULL;
16853         }
16854
16855         storedGames++;
16856         forwardMostMove = firstMove; // truncate game so we can start variation
16857 }
16858
16859 void
16860 PushTail (int firstMove, int lastMove)
16861 {
16862         if(appData.icsActive) { // only in local mode
16863                 forwardMostMove = currentMove; // mimic old ICS behavior
16864                 return;
16865         }
16866         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16867
16868         PushInner(firstMove, lastMove);
16869         if(storedGames == 1) GreyRevert(FALSE);
16870         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16871 }
16872
16873 void
16874 PopInner (Boolean annotate)
16875 {
16876         int i, j, nrMoves;
16877         char buf[8000], moveBuf[20];
16878
16879         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16880         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16881         nrMoves = savedLast[storedGames] - currentMove;
16882         if(annotate) {
16883                 int cnt = 10;
16884                 if(!WhiteOnMove(currentMove))
16885                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16886                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16887                 for(i=currentMove; i<forwardMostMove; i++) {
16888                         if(WhiteOnMove(i))
16889                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16890                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16891                         strcat(buf, moveBuf);
16892                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16893                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16894                 }
16895                 strcat(buf, ")");
16896         }
16897         for(i=1; i<=nrMoves; i++) { // copy last variation back
16898             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16899             for(j=0; j<MOVE_LEN; j++)
16900                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16901             for(j=0; j<2*MOVE_LEN; j++)
16902                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16903             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16904             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16905             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16906             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16907             commentList[currentMove+i] = commentList[framePtr+i];
16908             commentList[framePtr+i] = NULL;
16909         }
16910         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16911         framePtr = savedFramePtr[storedGames];
16912         gameInfo.result = savedResult[storedGames];
16913         if(gameInfo.resultDetails != NULL) {
16914             free(gameInfo.resultDetails);
16915       }
16916         gameInfo.resultDetails = savedDetails[storedGames];
16917         forwardMostMove = currentMove + nrMoves;
16918 }
16919
16920 Boolean
16921 PopTail (Boolean annotate)
16922 {
16923         if(appData.icsActive) return FALSE; // only in local mode
16924         if(!storedGames) return FALSE; // sanity
16925         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16926
16927         PopInner(annotate);
16928         if(currentMove < forwardMostMove) ForwardEvent(); else
16929         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16930
16931         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16932         return TRUE;
16933 }
16934
16935 void
16936 CleanupTail ()
16937 {       // remove all shelved variations
16938         int i;
16939         for(i=0; i<storedGames; i++) {
16940             if(savedDetails[i])
16941                 free(savedDetails[i]);
16942             savedDetails[i] = NULL;
16943         }
16944         for(i=framePtr; i<MAX_MOVES; i++) {
16945                 if(commentList[i]) free(commentList[i]);
16946                 commentList[i] = NULL;
16947         }
16948         framePtr = MAX_MOVES-1;
16949         storedGames = 0;
16950 }
16951
16952 void
16953 LoadVariation (int index, char *text)
16954 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16955         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16956         int level = 0, move;
16957
16958         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16959         // first find outermost bracketing variation
16960         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16961             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16962                 if(*p == '{') wait = '}'; else
16963                 if(*p == '[') wait = ']'; else
16964                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16965                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16966             }
16967             if(*p == wait) wait = NULLCHAR; // closing ]} found
16968             p++;
16969         }
16970         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16971         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16972         end[1] = NULLCHAR; // clip off comment beyond variation
16973         ToNrEvent(currentMove-1);
16974         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16975         // kludge: use ParsePV() to append variation to game
16976         move = currentMove;
16977         ParsePV(start, TRUE, TRUE);
16978         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16979         ClearPremoveHighlights();
16980         CommentPopDown();
16981         ToNrEvent(currentMove+1);
16982 }
16983