Allow grouping of engines in engine list
[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, "all");
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 int
9881 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
9882 {
9883     char buf[MSG_SIZ], *p, *q;
9884     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
9885     skip = !all && group[0]; // if group requested, we start in skip mode
9886     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
9887         p = names; q = buf; header = 0;
9888         while(*p && *p != '\n') *q++ = *p++;
9889         *q = 0;
9890         if(*p == '\n') p++;
9891         if(buf[0] == '#') {
9892             if(strstr(buf, "# end") == buf) { depth--; continue; } // leave group, and suppress printing label
9893             depth++; // we must be entering a new group
9894             if(all) continue; // suppress printing group headers when complete list requested
9895             header = 1;
9896             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
9897         }
9898         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
9899         if(engineList[i]) free(engineList[i]);
9900         engineList[i] = strdup(buf);
9901         if(buf[0] != '#') TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
9902         if(engineMnemonic[i]) free(engineMnemonic[i]);
9903         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9904             strcat(buf, " (");
9905             sscanf(q + 8, "%s", buf + strlen(buf));
9906             strcat(buf, ")");
9907         }
9908         engineMnemonic[i] = strdup(buf);
9909         i++;
9910     }
9911     engineList[i] = engineMnemonic[i] = NULL;
9912     return i;
9913 }
9914
9915 // following implemented as macro to avoid type limitations
9916 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9917
9918 void
9919 SwapEngines (int n)
9920 {   // swap settings for first engine and other engine (so far only some selected options)
9921     int h;
9922     char *p;
9923     if(n == 0) return;
9924     SWAP(directory, p)
9925     SWAP(chessProgram, p)
9926     SWAP(isUCI, h)
9927     SWAP(hasOwnBookUCI, h)
9928     SWAP(protocolVersion, h)
9929     SWAP(reuse, h)
9930     SWAP(scoreIsAbsolute, h)
9931     SWAP(timeOdds, h)
9932     SWAP(logo, p)
9933     SWAP(pgnName, p)
9934     SWAP(pvSAN, h)
9935     SWAP(engOptions, p)
9936 }
9937
9938 int
9939 SetPlayer (int player, char *p)
9940 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9941     int i;
9942     char buf[MSG_SIZ], *engineName;
9943     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9944     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9945     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9946     if(mnemonic[i]) {
9947         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9948         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9949         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
9950         ParseArgsFromString(buf);
9951     }
9952     free(engineName);
9953     return i;
9954 }
9955
9956 char *recentEngines;
9957
9958 void
9959 RecentEngineEvent (int nr)
9960 {
9961     int n;
9962 //    SwapEngines(1); // bump first to second
9963 //    ReplaceEngine(&second, 1); // and load it there
9964     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
9965     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
9966     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
9967         ReplaceEngine(&first, 0);
9968         FloatToFront(&appData.recentEngineList, command[n]);
9969     }
9970 }
9971
9972 int
9973 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9974 {   // determine players from game number
9975     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9976
9977     if(appData.tourneyType == 0) {
9978         roundsPerCycle = (nPlayers - 1) | 1;
9979         pairingsPerRound = nPlayers / 2;
9980     } else if(appData.tourneyType > 0) {
9981         roundsPerCycle = nPlayers - appData.tourneyType;
9982         pairingsPerRound = appData.tourneyType;
9983     }
9984     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9985     gamesPerCycle = gamesPerRound * roundsPerCycle;
9986     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9987     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9988     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9989     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9990     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9991     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9992
9993     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9994     if(appData.roundSync) *syncInterval = gamesPerRound;
9995
9996     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9997
9998     if(appData.tourneyType == 0) {
9999         if(curPairing == (nPlayers-1)/2 ) {
10000             *whitePlayer = curRound;
10001             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10002         } else {
10003             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10004             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10005             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10006             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10007         }
10008     } else if(appData.tourneyType > 1) {
10009         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10010         *whitePlayer = curRound + appData.tourneyType;
10011     } else if(appData.tourneyType > 0) {
10012         *whitePlayer = curPairing;
10013         *blackPlayer = curRound + appData.tourneyType;
10014     }
10015
10016     // take care of white/black alternation per round. 
10017     // For cycles and games this is already taken care of by default, derived from matchGame!
10018     return curRound & 1;
10019 }
10020
10021 int
10022 NextTourneyGame (int nr, int *swapColors)
10023 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10024     char *p, *q;
10025     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10026     FILE *tf;
10027     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10028     tf = fopen(appData.tourneyFile, "r");
10029     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10030     ParseArgsFromFile(tf); fclose(tf);
10031     InitTimeControls(); // TC might be altered from tourney file
10032
10033     nPlayers = CountPlayers(appData.participants); // count participants
10034     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10035     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10036
10037     if(syncInterval) {
10038         p = q = appData.results;
10039         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10040         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10041             DisplayMessage(_("Waiting for other game(s)"),"");
10042             waitingForGame = TRUE;
10043             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10044             return 0;
10045         }
10046         waitingForGame = FALSE;
10047     }
10048
10049     if(appData.tourneyType < 0) {
10050         if(nr>=0 && !pairingReceived) {
10051             char buf[1<<16];
10052             if(pairing.pr == NoProc) {
10053                 if(!appData.pairingEngine[0]) {
10054                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10055                     return 0;
10056                 }
10057                 StartChessProgram(&pairing); // starts the pairing engine
10058             }
10059             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10060             SendToProgram(buf, &pairing);
10061             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10062             SendToProgram(buf, &pairing);
10063             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10064         }
10065         pairingReceived = 0;                              // ... so we continue here 
10066         *swapColors = 0;
10067         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10068         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10069         matchGame = 1; roundNr = nr / syncInterval + 1;
10070     }
10071
10072     if(first.pr != NoProc && second.pr != NoProc) return 1; // engines already loaded
10073
10074     // redefine engines, engine dir, etc.
10075     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10076     if(first.pr == NoProc || nr < 0) {
10077       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10078       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10079     }
10080     if(second.pr == NoProc) {
10081       SwapEngines(1);
10082       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10083       SwapEngines(1);         // and make that valid for second engine by swapping
10084       InitEngine(&second, 1);
10085     }
10086     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10087     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10088     return 1;
10089 }
10090
10091 void
10092 NextMatchGame ()
10093 {   // performs game initialization that does not invoke engines, and then tries to start the game
10094     int res, firstWhite, swapColors = 0;
10095     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10096     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10097     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10098     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10099     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10100     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10101     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10102     Reset(FALSE, first.pr != NoProc);
10103     res = LoadGameOrPosition(matchGame); // setup game
10104     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10105     if(!res) return; // abort when bad game/pos file
10106     TwoMachinesEvent();
10107 }
10108
10109 void
10110 UserAdjudicationEvent (int result)
10111 {
10112     ChessMove gameResult = GameIsDrawn;
10113
10114     if( result > 0 ) {
10115         gameResult = WhiteWins;
10116     }
10117     else if( result < 0 ) {
10118         gameResult = BlackWins;
10119     }
10120
10121     if( gameMode == TwoMachinesPlay ) {
10122         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10123     }
10124 }
10125
10126
10127 // [HGM] save: calculate checksum of game to make games easily identifiable
10128 int
10129 StringCheckSum (char *s)
10130 {
10131         int i = 0;
10132         if(s==NULL) return 0;
10133         while(*s) i = i*259 + *s++;
10134         return i;
10135 }
10136
10137 int
10138 GameCheckSum ()
10139 {
10140         int i, sum=0;
10141         for(i=backwardMostMove; i<forwardMostMove; i++) {
10142                 sum += pvInfoList[i].depth;
10143                 sum += StringCheckSum(parseList[i]);
10144                 sum += StringCheckSum(commentList[i]);
10145                 sum *= 261;
10146         }
10147         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10148         return sum + StringCheckSum(commentList[i]);
10149 } // end of save patch
10150
10151 void
10152 GameEnds (ChessMove result, char *resultDetails, int whosays)
10153 {
10154     GameMode nextGameMode;
10155     int isIcsGame;
10156     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10157
10158     if(endingGame) return; /* [HGM] crash: forbid recursion */
10159     endingGame = 1;
10160     if(twoBoards) { // [HGM] dual: switch back to one board
10161         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10162         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10163     }
10164     if (appData.debugMode) {
10165       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10166               result, resultDetails ? resultDetails : "(null)", whosays);
10167     }
10168
10169     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10170
10171     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10172         /* If we are playing on ICS, the server decides when the
10173            game is over, but the engine can offer to draw, claim
10174            a draw, or resign.
10175          */
10176 #if ZIPPY
10177         if (appData.zippyPlay && first.initDone) {
10178             if (result == GameIsDrawn) {
10179                 /* In case draw still needs to be claimed */
10180                 SendToICS(ics_prefix);
10181                 SendToICS("draw\n");
10182             } else if (StrCaseStr(resultDetails, "resign")) {
10183                 SendToICS(ics_prefix);
10184                 SendToICS("resign\n");
10185             }
10186         }
10187 #endif
10188         endingGame = 0; /* [HGM] crash */
10189         return;
10190     }
10191
10192     /* If we're loading the game from a file, stop */
10193     if (whosays == GE_FILE) {
10194       (void) StopLoadGameTimer();
10195       gameFileFP = NULL;
10196     }
10197
10198     /* Cancel draw offers */
10199     first.offeredDraw = second.offeredDraw = 0;
10200
10201     /* If this is an ICS game, only ICS can really say it's done;
10202        if not, anyone can. */
10203     isIcsGame = (gameMode == IcsPlayingWhite ||
10204                  gameMode == IcsPlayingBlack ||
10205                  gameMode == IcsObserving    ||
10206                  gameMode == IcsExamining);
10207
10208     if (!isIcsGame || whosays == GE_ICS) {
10209         /* OK -- not an ICS game, or ICS said it was done */
10210         StopClocks();
10211         if (!isIcsGame && !appData.noChessProgram)
10212           SetUserThinkingEnables();
10213
10214         /* [HGM] if a machine claims the game end we verify this claim */
10215         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10216             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10217                 char claimer;
10218                 ChessMove trueResult = (ChessMove) -1;
10219
10220                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10221                                             first.twoMachinesColor[0] :
10222                                             second.twoMachinesColor[0] ;
10223
10224                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10225                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10226                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10227                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10228                 } else
10229                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10230                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10231                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10232                 } else
10233                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10234                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10235                 }
10236
10237                 // now verify win claims, but not in drop games, as we don't understand those yet
10238                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10239                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10240                     (result == WhiteWins && claimer == 'w' ||
10241                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10242                       if (appData.debugMode) {
10243                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10244                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10245                       }
10246                       if(result != trueResult) {
10247                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10248                               result = claimer == 'w' ? BlackWins : WhiteWins;
10249                               resultDetails = buf;
10250                       }
10251                 } else
10252                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10253                     && (forwardMostMove <= backwardMostMove ||
10254                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10255                         (claimer=='b')==(forwardMostMove&1))
10256                                                                                   ) {
10257                       /* [HGM] verify: draws that were not flagged are false claims */
10258                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10259                       result = claimer == 'w' ? BlackWins : WhiteWins;
10260                       resultDetails = buf;
10261                 }
10262                 /* (Claiming a loss is accepted no questions asked!) */
10263             }
10264             /* [HGM] bare: don't allow bare King to win */
10265             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10266                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10267                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10268                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10269                && result != GameIsDrawn)
10270             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10271                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10272                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10273                         if(p >= 0 && p <= (int)WhiteKing) k++;
10274                 }
10275                 if (appData.debugMode) {
10276                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10277                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10278                 }
10279                 if(k <= 1) {
10280                         result = GameIsDrawn;
10281                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10282                         resultDetails = buf;
10283                 }
10284             }
10285         }
10286
10287
10288         if(serverMoves != NULL && !loadFlag) { char c = '=';
10289             if(result==WhiteWins) c = '+';
10290             if(result==BlackWins) c = '-';
10291             if(resultDetails != NULL)
10292                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10293         }
10294         if (resultDetails != NULL) {
10295             gameInfo.result = result;
10296             gameInfo.resultDetails = StrSave(resultDetails);
10297
10298             /* display last move only if game was not loaded from file */
10299             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10300                 DisplayMove(currentMove - 1);
10301
10302             if (forwardMostMove != 0) {
10303                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10304                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10305                                                                 ) {
10306                     if (*appData.saveGameFile != NULLCHAR) {
10307                         SaveGameToFile(appData.saveGameFile, TRUE);
10308                     } else if (appData.autoSaveGames) {
10309                         AutoSaveGame();
10310                     }
10311                     if (*appData.savePositionFile != NULLCHAR) {
10312                         SavePositionToFile(appData.savePositionFile);
10313                     }
10314                 }
10315             }
10316
10317             /* Tell program how game ended in case it is learning */
10318             /* [HGM] Moved this to after saving the PGN, just in case */
10319             /* engine died and we got here through time loss. In that */
10320             /* case we will get a fatal error writing the pipe, which */
10321             /* would otherwise lose us the PGN.                       */
10322             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10323             /* output during GameEnds should never be fatal anymore   */
10324             if (gameMode == MachinePlaysWhite ||
10325                 gameMode == MachinePlaysBlack ||
10326                 gameMode == TwoMachinesPlay ||
10327                 gameMode == IcsPlayingWhite ||
10328                 gameMode == IcsPlayingBlack ||
10329                 gameMode == BeginningOfGame) {
10330                 char buf[MSG_SIZ];
10331                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10332                         resultDetails);
10333                 if (first.pr != NoProc) {
10334                     SendToProgram(buf, &first);
10335                 }
10336                 if (second.pr != NoProc &&
10337                     gameMode == TwoMachinesPlay) {
10338                     SendToProgram(buf, &second);
10339                 }
10340             }
10341         }
10342
10343         if (appData.icsActive) {
10344             if (appData.quietPlay &&
10345                 (gameMode == IcsPlayingWhite ||
10346                  gameMode == IcsPlayingBlack)) {
10347                 SendToICS(ics_prefix);
10348                 SendToICS("set shout 1\n");
10349             }
10350             nextGameMode = IcsIdle;
10351             ics_user_moved = FALSE;
10352             /* clean up premove.  It's ugly when the game has ended and the
10353              * premove highlights are still on the board.
10354              */
10355             if (gotPremove) {
10356               gotPremove = FALSE;
10357               ClearPremoveHighlights();
10358               DrawPosition(FALSE, boards[currentMove]);
10359             }
10360             if (whosays == GE_ICS) {
10361                 switch (result) {
10362                 case WhiteWins:
10363                     if (gameMode == IcsPlayingWhite)
10364                         PlayIcsWinSound();
10365                     else if(gameMode == IcsPlayingBlack)
10366                         PlayIcsLossSound();
10367                     break;
10368                 case BlackWins:
10369                     if (gameMode == IcsPlayingBlack)
10370                         PlayIcsWinSound();
10371                     else if(gameMode == IcsPlayingWhite)
10372                         PlayIcsLossSound();
10373                     break;
10374                 case GameIsDrawn:
10375                     PlayIcsDrawSound();
10376                     break;
10377                 default:
10378                     PlayIcsUnfinishedSound();
10379                 }
10380             }
10381         } else if (gameMode == EditGame ||
10382                    gameMode == PlayFromGameFile ||
10383                    gameMode == AnalyzeMode ||
10384                    gameMode == AnalyzeFile) {
10385             nextGameMode = gameMode;
10386         } else {
10387             nextGameMode = EndOfGame;
10388         }
10389         pausing = FALSE;
10390         ModeHighlight();
10391     } else {
10392         nextGameMode = gameMode;
10393     }
10394
10395     if (appData.noChessProgram) {
10396         gameMode = nextGameMode;
10397         ModeHighlight();
10398         endingGame = 0; /* [HGM] crash */
10399         return;
10400     }
10401
10402     if (first.reuse) {
10403         /* Put first chess program into idle state */
10404         if (first.pr != NoProc &&
10405             (gameMode == MachinePlaysWhite ||
10406              gameMode == MachinePlaysBlack ||
10407              gameMode == TwoMachinesPlay ||
10408              gameMode == IcsPlayingWhite ||
10409              gameMode == IcsPlayingBlack ||
10410              gameMode == BeginningOfGame)) {
10411             SendToProgram("force\n", &first);
10412             if (first.usePing) {
10413               char buf[MSG_SIZ];
10414               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10415               SendToProgram(buf, &first);
10416             }
10417         }
10418     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10419         /* Kill off first chess program */
10420         if (first.isr != NULL)
10421           RemoveInputSource(first.isr);
10422         first.isr = NULL;
10423
10424         if (first.pr != NoProc) {
10425             ExitAnalyzeMode();
10426             DoSleep( appData.delayBeforeQuit );
10427             SendToProgram("quit\n", &first);
10428             DoSleep( appData.delayAfterQuit );
10429             DestroyChildProcess(first.pr, first.useSigterm);
10430         }
10431         first.pr = NoProc;
10432     }
10433     if (second.reuse) {
10434         /* Put second chess program into idle state */
10435         if (second.pr != NoProc &&
10436             gameMode == TwoMachinesPlay) {
10437             SendToProgram("force\n", &second);
10438             if (second.usePing) {
10439               char buf[MSG_SIZ];
10440               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10441               SendToProgram(buf, &second);
10442             }
10443         }
10444     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10445         /* Kill off second chess program */
10446         if (second.isr != NULL)
10447           RemoveInputSource(second.isr);
10448         second.isr = NULL;
10449
10450         if (second.pr != NoProc) {
10451             DoSleep( appData.delayBeforeQuit );
10452             SendToProgram("quit\n", &second);
10453             DoSleep( appData.delayAfterQuit );
10454             DestroyChildProcess(second.pr, second.useSigterm);
10455         }
10456         second.pr = NoProc;
10457     }
10458
10459     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10460         char resChar = '=';
10461         switch (result) {
10462         case WhiteWins:
10463           resChar = '+';
10464           if (first.twoMachinesColor[0] == 'w') {
10465             first.matchWins++;
10466           } else {
10467             second.matchWins++;
10468           }
10469           break;
10470         case BlackWins:
10471           resChar = '-';
10472           if (first.twoMachinesColor[0] == 'b') {
10473             first.matchWins++;
10474           } else {
10475             second.matchWins++;
10476           }
10477           break;
10478         case GameUnfinished:
10479           resChar = ' ';
10480         default:
10481           break;
10482         }
10483
10484         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10485         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10486             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10487             ReserveGame(nextGame, resChar); // sets nextGame
10488             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10489             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10490         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10491
10492         if (nextGame <= appData.matchGames && !abortMatch) {
10493             gameMode = nextGameMode;
10494             matchGame = nextGame; // this will be overruled in tourney mode!
10495             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10496             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10497             endingGame = 0; /* [HGM] crash */
10498             return;
10499         } else {
10500             gameMode = nextGameMode;
10501             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10502                      first.tidy, second.tidy,
10503                      first.matchWins, second.matchWins,
10504                      appData.matchGames - (first.matchWins + second.matchWins));
10505             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10506             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10507             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10508             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10509                 first.twoMachinesColor = "black\n";
10510                 second.twoMachinesColor = "white\n";
10511             } else {
10512                 first.twoMachinesColor = "white\n";
10513                 second.twoMachinesColor = "black\n";
10514             }
10515         }
10516     }
10517     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10518         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10519       ExitAnalyzeMode();
10520     gameMode = nextGameMode;
10521     ModeHighlight();
10522     endingGame = 0;  /* [HGM] crash */
10523     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10524         if(matchMode == TRUE) { // match through command line: exit with or without popup
10525             if(ranking) {
10526                 ToNrEvent(forwardMostMove);
10527                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10528                 else ExitEvent(0);
10529             } else DisplayFatalError(buf, 0, 0);
10530         } else { // match through menu; just stop, with or without popup
10531             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10532             ModeHighlight();
10533             if(ranking){
10534                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10535             } else DisplayNote(buf);
10536       }
10537       if(ranking) free(ranking);
10538     }
10539 }
10540
10541 /* Assumes program was just initialized (initString sent).
10542    Leaves program in force mode. */
10543 void
10544 FeedMovesToProgram (ChessProgramState *cps, int upto)
10545 {
10546     int i;
10547
10548     if (appData.debugMode)
10549       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10550               startedFromSetupPosition ? "position and " : "",
10551               backwardMostMove, upto, cps->which);
10552     if(currentlyInitializedVariant != gameInfo.variant) {
10553       char buf[MSG_SIZ];
10554         // [HGM] variantswitch: make engine aware of new variant
10555         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10556                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10557         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10558         SendToProgram(buf, cps);
10559         currentlyInitializedVariant = gameInfo.variant;
10560     }
10561     SendToProgram("force\n", cps);
10562     if (startedFromSetupPosition) {
10563         SendBoard(cps, backwardMostMove);
10564     if (appData.debugMode) {
10565         fprintf(debugFP, "feedMoves\n");
10566     }
10567     }
10568     for (i = backwardMostMove; i < upto; i++) {
10569         SendMoveToProgram(i, cps);
10570     }
10571 }
10572
10573
10574 int
10575 ResurrectChessProgram ()
10576 {
10577      /* The chess program may have exited.
10578         If so, restart it and feed it all the moves made so far. */
10579     static int doInit = 0;
10580
10581     if (appData.noChessProgram) return 1;
10582
10583     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10584         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10585         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10586         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10587     } else {
10588         if (first.pr != NoProc) return 1;
10589         StartChessProgram(&first);
10590     }
10591     InitChessProgram(&first, FALSE);
10592     FeedMovesToProgram(&first, currentMove);
10593
10594     if (!first.sendTime) {
10595         /* can't tell gnuchess what its clock should read,
10596            so we bow to its notion. */
10597         ResetClocks();
10598         timeRemaining[0][currentMove] = whiteTimeRemaining;
10599         timeRemaining[1][currentMove] = blackTimeRemaining;
10600     }
10601
10602     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10603                 appData.icsEngineAnalyze) && first.analysisSupport) {
10604       SendToProgram("analyze\n", &first);
10605       first.analyzing = TRUE;
10606     }
10607     return 1;
10608 }
10609
10610 /*
10611  * Button procedures
10612  */
10613 void
10614 Reset (int redraw, int init)
10615 {
10616     int i;
10617
10618     if (appData.debugMode) {
10619         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10620                 redraw, init, gameMode);
10621     }
10622     CleanupTail(); // [HGM] vari: delete any stored variations
10623     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10624     pausing = pauseExamInvalid = FALSE;
10625     startedFromSetupPosition = blackPlaysFirst = FALSE;
10626     firstMove = TRUE;
10627     whiteFlag = blackFlag = FALSE;
10628     userOfferedDraw = FALSE;
10629     hintRequested = bookRequested = FALSE;
10630     first.maybeThinking = FALSE;
10631     second.maybeThinking = FALSE;
10632     first.bookSuspend = FALSE; // [HGM] book
10633     second.bookSuspend = FALSE;
10634     thinkOutput[0] = NULLCHAR;
10635     lastHint[0] = NULLCHAR;
10636     ClearGameInfo(&gameInfo);
10637     gameInfo.variant = StringToVariant(appData.variant);
10638     ics_user_moved = ics_clock_paused = FALSE;
10639     ics_getting_history = H_FALSE;
10640     ics_gamenum = -1;
10641     white_holding[0] = black_holding[0] = NULLCHAR;
10642     ClearProgramStats();
10643     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10644
10645     ResetFrontEnd();
10646     ClearHighlights();
10647     flipView = appData.flipView;
10648     ClearPremoveHighlights();
10649     gotPremove = FALSE;
10650     alarmSounded = FALSE;
10651
10652     GameEnds(EndOfFile, NULL, GE_PLAYER);
10653     if(appData.serverMovesName != NULL) {
10654         /* [HGM] prepare to make moves file for broadcasting */
10655         clock_t t = clock();
10656         if(serverMoves != NULL) fclose(serverMoves);
10657         serverMoves = fopen(appData.serverMovesName, "r");
10658         if(serverMoves != NULL) {
10659             fclose(serverMoves);
10660             /* delay 15 sec before overwriting, so all clients can see end */
10661             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10662         }
10663         serverMoves = fopen(appData.serverMovesName, "w");
10664     }
10665
10666     ExitAnalyzeMode();
10667     gameMode = BeginningOfGame;
10668     ModeHighlight();
10669     if(appData.icsActive) gameInfo.variant = VariantNormal;
10670     currentMove = forwardMostMove = backwardMostMove = 0;
10671     MarkTargetSquares(1);
10672     InitPosition(redraw);
10673     for (i = 0; i < MAX_MOVES; i++) {
10674         if (commentList[i] != NULL) {
10675             free(commentList[i]);
10676             commentList[i] = NULL;
10677         }
10678     }
10679     ResetClocks();
10680     timeRemaining[0][0] = whiteTimeRemaining;
10681     timeRemaining[1][0] = blackTimeRemaining;
10682
10683     if (first.pr == NoProc) {
10684         StartChessProgram(&first);
10685     }
10686     if (init) {
10687             InitChessProgram(&first, startedFromSetupPosition);
10688     }
10689     DisplayTitle("");
10690     DisplayMessage("", "");
10691     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10692     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10693 }
10694
10695 void
10696 AutoPlayGameLoop ()
10697 {
10698     for (;;) {
10699         if (!AutoPlayOneMove())
10700           return;
10701         if (matchMode || appData.timeDelay == 0)
10702           continue;
10703         if (appData.timeDelay < 0)
10704           return;
10705         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10706         break;
10707     }
10708 }
10709
10710
10711 int
10712 AutoPlayOneMove ()
10713 {
10714     int fromX, fromY, toX, toY;
10715
10716     if (appData.debugMode) {
10717       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10718     }
10719
10720     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10721       return FALSE;
10722
10723     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10724       pvInfoList[currentMove].depth = programStats.depth;
10725       pvInfoList[currentMove].score = programStats.score;
10726       pvInfoList[currentMove].time  = 0;
10727       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10728     }
10729
10730     if (currentMove >= forwardMostMove) {
10731       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10732 //      gameMode = EndOfGame;
10733 //      ModeHighlight();
10734
10735       /* [AS] Clear current move marker at the end of a game */
10736       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10737
10738       return FALSE;
10739     }
10740
10741     toX = moveList[currentMove][2] - AAA;
10742     toY = moveList[currentMove][3] - ONE;
10743
10744     if (moveList[currentMove][1] == '@') {
10745         if (appData.highlightLastMove) {
10746             SetHighlights(-1, -1, toX, toY);
10747         }
10748     } else {
10749         fromX = moveList[currentMove][0] - AAA;
10750         fromY = moveList[currentMove][1] - ONE;
10751
10752         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10753
10754         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10755
10756         if (appData.highlightLastMove) {
10757             SetHighlights(fromX, fromY, toX, toY);
10758         }
10759     }
10760     DisplayMove(currentMove);
10761     SendMoveToProgram(currentMove++, &first);
10762     DisplayBothClocks();
10763     DrawPosition(FALSE, boards[currentMove]);
10764     // [HGM] PV info: always display, routine tests if empty
10765     DisplayComment(currentMove - 1, commentList[currentMove]);
10766     return TRUE;
10767 }
10768
10769
10770 int
10771 LoadGameOneMove (ChessMove readAhead)
10772 {
10773     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10774     char promoChar = NULLCHAR;
10775     ChessMove moveType;
10776     char move[MSG_SIZ];
10777     char *p, *q;
10778
10779     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10780         gameMode != AnalyzeMode && gameMode != Training) {
10781         gameFileFP = NULL;
10782         return FALSE;
10783     }
10784
10785     yyboardindex = forwardMostMove;
10786     if (readAhead != EndOfFile) {
10787       moveType = readAhead;
10788     } else {
10789       if (gameFileFP == NULL)
10790           return FALSE;
10791       moveType = (ChessMove) Myylex();
10792     }
10793
10794     done = FALSE;
10795     switch (moveType) {
10796       case Comment:
10797         if (appData.debugMode)
10798           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10799         p = yy_text;
10800
10801         /* append the comment but don't display it */
10802         AppendComment(currentMove, p, FALSE);
10803         return TRUE;
10804
10805       case WhiteCapturesEnPassant:
10806       case BlackCapturesEnPassant:
10807       case WhitePromotion:
10808       case BlackPromotion:
10809       case WhiteNonPromotion:
10810       case BlackNonPromotion:
10811       case NormalMove:
10812       case WhiteKingSideCastle:
10813       case WhiteQueenSideCastle:
10814       case BlackKingSideCastle:
10815       case BlackQueenSideCastle:
10816       case WhiteKingSideCastleWild:
10817       case WhiteQueenSideCastleWild:
10818       case BlackKingSideCastleWild:
10819       case BlackQueenSideCastleWild:
10820       /* PUSH Fabien */
10821       case WhiteHSideCastleFR:
10822       case WhiteASideCastleFR:
10823       case BlackHSideCastleFR:
10824       case BlackASideCastleFR:
10825       /* POP Fabien */
10826         if (appData.debugMode)
10827           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10828         fromX = currentMoveString[0] - AAA;
10829         fromY = currentMoveString[1] - ONE;
10830         toX = currentMoveString[2] - AAA;
10831         toY = currentMoveString[3] - ONE;
10832         promoChar = currentMoveString[4];
10833         break;
10834
10835       case WhiteDrop:
10836       case BlackDrop:
10837         if (appData.debugMode)
10838           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10839         fromX = moveType == WhiteDrop ?
10840           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10841         (int) CharToPiece(ToLower(currentMoveString[0]));
10842         fromY = DROP_RANK;
10843         toX = currentMoveString[2] - AAA;
10844         toY = currentMoveString[3] - ONE;
10845         break;
10846
10847       case WhiteWins:
10848       case BlackWins:
10849       case GameIsDrawn:
10850       case GameUnfinished:
10851         if (appData.debugMode)
10852           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10853         p = strchr(yy_text, '{');
10854         if (p == NULL) p = strchr(yy_text, '(');
10855         if (p == NULL) {
10856             p = yy_text;
10857             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10858         } else {
10859             q = strchr(p, *p == '{' ? '}' : ')');
10860             if (q != NULL) *q = NULLCHAR;
10861             p++;
10862         }
10863         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10864         GameEnds(moveType, p, GE_FILE);
10865         done = TRUE;
10866         if (cmailMsgLoaded) {
10867             ClearHighlights();
10868             flipView = WhiteOnMove(currentMove);
10869             if (moveType == GameUnfinished) flipView = !flipView;
10870             if (appData.debugMode)
10871               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10872         }
10873         break;
10874
10875       case EndOfFile:
10876         if (appData.debugMode)
10877           fprintf(debugFP, "Parser hit end of file\n");
10878         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10879           case MT_NONE:
10880           case MT_CHECK:
10881             break;
10882           case MT_CHECKMATE:
10883           case MT_STAINMATE:
10884             if (WhiteOnMove(currentMove)) {
10885                 GameEnds(BlackWins, "Black mates", GE_FILE);
10886             } else {
10887                 GameEnds(WhiteWins, "White mates", GE_FILE);
10888             }
10889             break;
10890           case MT_STALEMATE:
10891             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10892             break;
10893         }
10894         done = TRUE;
10895         break;
10896
10897       case MoveNumberOne:
10898         if (lastLoadGameStart == GNUChessGame) {
10899             /* GNUChessGames have numbers, but they aren't move numbers */
10900             if (appData.debugMode)
10901               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10902                       yy_text, (int) moveType);
10903             return LoadGameOneMove(EndOfFile); /* tail recursion */
10904         }
10905         /* else fall thru */
10906
10907       case XBoardGame:
10908       case GNUChessGame:
10909       case PGNTag:
10910         /* Reached start of next game in file */
10911         if (appData.debugMode)
10912           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10913         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10914           case MT_NONE:
10915           case MT_CHECK:
10916             break;
10917           case MT_CHECKMATE:
10918           case MT_STAINMATE:
10919             if (WhiteOnMove(currentMove)) {
10920                 GameEnds(BlackWins, "Black mates", GE_FILE);
10921             } else {
10922                 GameEnds(WhiteWins, "White mates", GE_FILE);
10923             }
10924             break;
10925           case MT_STALEMATE:
10926             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10927             break;
10928         }
10929         done = TRUE;
10930         break;
10931
10932       case PositionDiagram:     /* should not happen; ignore */
10933       case ElapsedTime:         /* ignore */
10934       case NAG:                 /* ignore */
10935         if (appData.debugMode)
10936           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10937                   yy_text, (int) moveType);
10938         return LoadGameOneMove(EndOfFile); /* tail recursion */
10939
10940       case IllegalMove:
10941         if (appData.testLegality) {
10942             if (appData.debugMode)
10943               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10944             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10945                     (forwardMostMove / 2) + 1,
10946                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10947             DisplayError(move, 0);
10948             done = TRUE;
10949         } else {
10950             if (appData.debugMode)
10951               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10952                       yy_text, currentMoveString);
10953             fromX = currentMoveString[0] - AAA;
10954             fromY = currentMoveString[1] - ONE;
10955             toX = currentMoveString[2] - AAA;
10956             toY = currentMoveString[3] - ONE;
10957             promoChar = currentMoveString[4];
10958         }
10959         break;
10960
10961       case AmbiguousMove:
10962         if (appData.debugMode)
10963           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10964         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10965                 (forwardMostMove / 2) + 1,
10966                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10967         DisplayError(move, 0);
10968         done = TRUE;
10969         break;
10970
10971       default:
10972       case ImpossibleMove:
10973         if (appData.debugMode)
10974           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10975         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10976                 (forwardMostMove / 2) + 1,
10977                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10978         DisplayError(move, 0);
10979         done = TRUE;
10980         break;
10981     }
10982
10983     if (done) {
10984         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10985             DrawPosition(FALSE, boards[currentMove]);
10986             DisplayBothClocks();
10987             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10988               DisplayComment(currentMove - 1, commentList[currentMove]);
10989         }
10990         (void) StopLoadGameTimer();
10991         gameFileFP = NULL;
10992         cmailOldMove = forwardMostMove;
10993         return FALSE;
10994     } else {
10995         /* currentMoveString is set as a side-effect of yylex */
10996
10997         thinkOutput[0] = NULLCHAR;
10998         MakeMove(fromX, fromY, toX, toY, promoChar);
10999         currentMove = forwardMostMove;
11000         return TRUE;
11001     }
11002 }
11003
11004 /* Load the nth game from the given file */
11005 int
11006 LoadGameFromFile (char *filename, int n, char *title, int useList)
11007 {
11008     FILE *f;
11009     char buf[MSG_SIZ];
11010
11011     if (strcmp(filename, "-") == 0) {
11012         f = stdin;
11013         title = "stdin";
11014     } else {
11015         f = fopen(filename, "rb");
11016         if (f == NULL) {
11017           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11018             DisplayError(buf, errno);
11019             return FALSE;
11020         }
11021     }
11022     if (fseek(f, 0, 0) == -1) {
11023         /* f is not seekable; probably a pipe */
11024         useList = FALSE;
11025     }
11026     if (useList && n == 0) {
11027         int error = GameListBuild(f);
11028         if (error) {
11029             DisplayError(_("Cannot build game list"), error);
11030         } else if (!ListEmpty(&gameList) &&
11031                    ((ListGame *) gameList.tailPred)->number > 1) {
11032             GameListPopUp(f, title);
11033             return TRUE;
11034         }
11035         GameListDestroy();
11036         n = 1;
11037     }
11038     if (n == 0) n = 1;
11039     return LoadGame(f, n, title, FALSE);
11040 }
11041
11042
11043 void
11044 MakeRegisteredMove ()
11045 {
11046     int fromX, fromY, toX, toY;
11047     char promoChar;
11048     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11049         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11050           case CMAIL_MOVE:
11051           case CMAIL_DRAW:
11052             if (appData.debugMode)
11053               fprintf(debugFP, "Restoring %s for game %d\n",
11054                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11055
11056             thinkOutput[0] = NULLCHAR;
11057             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11058             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11059             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11060             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11061             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11062             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11063             MakeMove(fromX, fromY, toX, toY, promoChar);
11064             ShowMove(fromX, fromY, toX, toY);
11065
11066             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11067               case MT_NONE:
11068               case MT_CHECK:
11069                 break;
11070
11071               case MT_CHECKMATE:
11072               case MT_STAINMATE:
11073                 if (WhiteOnMove(currentMove)) {
11074                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11075                 } else {
11076                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11077                 }
11078                 break;
11079
11080               case MT_STALEMATE:
11081                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11082                 break;
11083             }
11084
11085             break;
11086
11087           case CMAIL_RESIGN:
11088             if (WhiteOnMove(currentMove)) {
11089                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11090             } else {
11091                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11092             }
11093             break;
11094
11095           case CMAIL_ACCEPT:
11096             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11097             break;
11098
11099           default:
11100             break;
11101         }
11102     }
11103
11104     return;
11105 }
11106
11107 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11108 int
11109 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11110 {
11111     int retVal;
11112
11113     if (gameNumber > nCmailGames) {
11114         DisplayError(_("No more games in this message"), 0);
11115         return FALSE;
11116     }
11117     if (f == lastLoadGameFP) {
11118         int offset = gameNumber - lastLoadGameNumber;
11119         if (offset == 0) {
11120             cmailMsg[0] = NULLCHAR;
11121             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11122                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11123                 nCmailMovesRegistered--;
11124             }
11125             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11126             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11127                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11128             }
11129         } else {
11130             if (! RegisterMove()) return FALSE;
11131         }
11132     }
11133
11134     retVal = LoadGame(f, gameNumber, title, useList);
11135
11136     /* Make move registered during previous look at this game, if any */
11137     MakeRegisteredMove();
11138
11139     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11140         commentList[currentMove]
11141           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11142         DisplayComment(currentMove - 1, commentList[currentMove]);
11143     }
11144
11145     return retVal;
11146 }
11147
11148 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11149 int
11150 ReloadGame (int offset)
11151 {
11152     int gameNumber = lastLoadGameNumber + offset;
11153     if (lastLoadGameFP == NULL) {
11154         DisplayError(_("No game has been loaded yet"), 0);
11155         return FALSE;
11156     }
11157     if (gameNumber <= 0) {
11158         DisplayError(_("Can't back up any further"), 0);
11159         return FALSE;
11160     }
11161     if (cmailMsgLoaded) {
11162         return CmailLoadGame(lastLoadGameFP, gameNumber,
11163                              lastLoadGameTitle, lastLoadGameUseList);
11164     } else {
11165         return LoadGame(lastLoadGameFP, gameNumber,
11166                         lastLoadGameTitle, lastLoadGameUseList);
11167     }
11168 }
11169
11170 int keys[EmptySquare+1];
11171
11172 int
11173 PositionMatches (Board b1, Board b2)
11174 {
11175     int r, f, sum=0;
11176     switch(appData.searchMode) {
11177         case 1: return CompareWithRights(b1, b2);
11178         case 2:
11179             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11180                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11181             }
11182             return TRUE;
11183         case 3:
11184             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11185               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11186                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11187             }
11188             return sum==0;
11189         case 4:
11190             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11191                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11192             }
11193             return sum==0;
11194     }
11195     return TRUE;
11196 }
11197
11198 #define Q_PROMO  4
11199 #define Q_EP     3
11200 #define Q_BCASTL 2
11201 #define Q_WCASTL 1
11202
11203 int pieceList[256], quickBoard[256];
11204 ChessSquare pieceType[256] = { EmptySquare };
11205 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11206 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11207 int soughtTotal, turn;
11208 Boolean epOK, flipSearch;
11209
11210 typedef struct {
11211     unsigned char piece, to;
11212 } Move;
11213
11214 #define DSIZE (250000)
11215
11216 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11217 Move *moveDatabase = initialSpace;
11218 unsigned int movePtr, dataSize = DSIZE;
11219
11220 int
11221 MakePieceList (Board board, int *counts)
11222 {
11223     int r, f, n=Q_PROMO, total=0;
11224     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11225     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11226         int sq = f + (r<<4);
11227         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11228             quickBoard[sq] = ++n;
11229             pieceList[n] = sq;
11230             pieceType[n] = board[r][f];
11231             counts[board[r][f]]++;
11232             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11233             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11234             total++;
11235         }
11236     }
11237     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11238     return total;
11239 }
11240
11241 void
11242 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11243 {
11244     int sq = fromX + (fromY<<4);
11245     int piece = quickBoard[sq];
11246     quickBoard[sq] = 0;
11247     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11248     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11249         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11250         moveDatabase[movePtr++].piece = Q_WCASTL;
11251         quickBoard[sq] = piece;
11252         piece = quickBoard[from]; quickBoard[from] = 0;
11253         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11254     } else
11255     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11256         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11257         moveDatabase[movePtr++].piece = Q_BCASTL;
11258         quickBoard[sq] = piece;
11259         piece = quickBoard[from]; quickBoard[from] = 0;
11260         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11261     } else
11262     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11263         quickBoard[(fromY<<4)+toX] = 0;
11264         moveDatabase[movePtr].piece = Q_EP;
11265         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11266         moveDatabase[movePtr].to = sq;
11267     } else
11268     if(promoPiece != pieceType[piece]) {
11269         moveDatabase[movePtr++].piece = Q_PROMO;
11270         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11271     }
11272     moveDatabase[movePtr].piece = piece;
11273     quickBoard[sq] = piece;
11274     movePtr++;
11275 }
11276
11277 int
11278 PackGame (Board board)
11279 {
11280     Move *newSpace = NULL;
11281     moveDatabase[movePtr].piece = 0; // terminate previous game
11282     if(movePtr > dataSize) {
11283         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11284         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11285         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11286         if(newSpace) {
11287             int i;
11288             Move *p = moveDatabase, *q = newSpace;
11289             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11290             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11291             moveDatabase = newSpace;
11292         } else { // calloc failed, we must be out of memory. Too bad...
11293             dataSize = 0; // prevent calloc events for all subsequent games
11294             return 0;     // and signal this one isn't cached
11295         }
11296     }
11297     movePtr++;
11298     MakePieceList(board, counts);
11299     return movePtr;
11300 }
11301
11302 int
11303 QuickCompare (Board board, int *minCounts, int *maxCounts)
11304 {   // compare according to search mode
11305     int r, f;
11306     switch(appData.searchMode)
11307     {
11308       case 1: // exact position match
11309         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11310         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11311             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11312         }
11313         break;
11314       case 2: // can have extra material on empty squares
11315         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11316             if(board[r][f] == EmptySquare) continue;
11317             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11318         }
11319         break;
11320       case 3: // material with exact Pawn structure
11321         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11322             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11323             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11324         } // fall through to material comparison
11325       case 4: // exact material
11326         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11327         break;
11328       case 6: // material range with given imbalance
11329         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11330         // fall through to range comparison
11331       case 5: // material range
11332         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11333     }
11334     return TRUE;
11335 }
11336
11337 int
11338 QuickScan (Board board, Move *move)
11339 {   // reconstruct game,and compare all positions in it
11340     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11341     do {
11342         int piece = move->piece;
11343         int to = move->to, from = pieceList[piece];
11344         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11345           if(!piece) return -1;
11346           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11347             piece = (++move)->piece;
11348             from = pieceList[piece];
11349             counts[pieceType[piece]]--;
11350             pieceType[piece] = (ChessSquare) move->to;
11351             counts[move->to]++;
11352           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11353             counts[pieceType[quickBoard[to]]]--;
11354             quickBoard[to] = 0; total--;
11355             move++;
11356             continue;
11357           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11358             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11359             from  = pieceList[piece]; // so this must be King
11360             quickBoard[from] = 0;
11361             quickBoard[to] = piece;
11362             pieceList[piece] = to;
11363             move++;
11364             continue;
11365           }
11366         }
11367         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11368         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11369         quickBoard[from] = 0;
11370         quickBoard[to] = piece;
11371         pieceList[piece] = to;
11372         cnt++; turn ^= 3;
11373         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11374            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11375            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11376                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11377           ) {
11378             static int lastCounts[EmptySquare+1];
11379             int i;
11380             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11381             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11382         } else stretch = 0;
11383         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11384         move++;
11385     } while(1);
11386 }
11387
11388 void
11389 InitSearch ()
11390 {
11391     int r, f;
11392     flipSearch = FALSE;
11393     CopyBoard(soughtBoard, boards[currentMove]);
11394     soughtTotal = MakePieceList(soughtBoard, maxSought);
11395     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11396     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11397     CopyBoard(reverseBoard, boards[currentMove]);
11398     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11399         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11400         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11401         reverseBoard[r][f] = piece;
11402     }
11403     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11404     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11405     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11406                  || (boards[currentMove][CASTLING][2] == NoRights || 
11407                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11408                  && (boards[currentMove][CASTLING][5] == NoRights || 
11409                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11410       ) {
11411         flipSearch = TRUE;
11412         CopyBoard(flipBoard, soughtBoard);
11413         CopyBoard(rotateBoard, reverseBoard);
11414         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11415             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11416             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11417         }
11418     }
11419     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11420     if(appData.searchMode >= 5) {
11421         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11422         MakePieceList(soughtBoard, minSought);
11423         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11424     }
11425     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11426         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11427 }
11428
11429 GameInfo dummyInfo;
11430
11431 int
11432 GameContainsPosition (FILE *f, ListGame *lg)
11433 {
11434     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11435     int fromX, fromY, toX, toY;
11436     char promoChar;
11437     static int initDone=FALSE;
11438
11439     // weed out games based on numerical tag comparison
11440     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11441     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11442     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11443     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11444     if(!initDone) {
11445         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11446         initDone = TRUE;
11447     }
11448     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11449     else CopyBoard(boards[scratch], initialPosition); // default start position
11450     if(lg->moves) {
11451         turn = btm + 1;
11452         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11453         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11454     }
11455     if(btm) plyNr++;
11456     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11457     fseek(f, lg->offset, 0);
11458     yynewfile(f);
11459     while(1) {
11460         yyboardindex = scratch;
11461         quickFlag = plyNr+1;
11462         next = Myylex();
11463         quickFlag = 0;
11464         switch(next) {
11465             case PGNTag:
11466                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11467             default:
11468                 continue;
11469
11470             case XBoardGame:
11471             case GNUChessGame:
11472                 if(plyNr) return -1; // after we have seen moves, this is for new game
11473               continue;
11474
11475             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11476             case ImpossibleMove:
11477             case WhiteWins: // game ends here with these four
11478             case BlackWins:
11479             case GameIsDrawn:
11480             case GameUnfinished:
11481                 return -1;
11482
11483             case IllegalMove:
11484                 if(appData.testLegality) return -1;
11485             case WhiteCapturesEnPassant:
11486             case BlackCapturesEnPassant:
11487             case WhitePromotion:
11488             case BlackPromotion:
11489             case WhiteNonPromotion:
11490             case BlackNonPromotion:
11491             case NormalMove:
11492             case WhiteKingSideCastle:
11493             case WhiteQueenSideCastle:
11494             case BlackKingSideCastle:
11495             case BlackQueenSideCastle:
11496             case WhiteKingSideCastleWild:
11497             case WhiteQueenSideCastleWild:
11498             case BlackKingSideCastleWild:
11499             case BlackQueenSideCastleWild:
11500             case WhiteHSideCastleFR:
11501             case WhiteASideCastleFR:
11502             case BlackHSideCastleFR:
11503             case BlackASideCastleFR:
11504                 fromX = currentMoveString[0] - AAA;
11505                 fromY = currentMoveString[1] - ONE;
11506                 toX = currentMoveString[2] - AAA;
11507                 toY = currentMoveString[3] - ONE;
11508                 promoChar = currentMoveString[4];
11509                 break;
11510             case WhiteDrop:
11511             case BlackDrop:
11512                 fromX = next == WhiteDrop ?
11513                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11514                   (int) CharToPiece(ToLower(currentMoveString[0]));
11515                 fromY = DROP_RANK;
11516                 toX = currentMoveString[2] - AAA;
11517                 toY = currentMoveString[3] - ONE;
11518                 promoChar = 0;
11519                 break;
11520         }
11521         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11522         plyNr++;
11523         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11524         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11525         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11526         if(appData.findMirror) {
11527             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11528             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11529         }
11530     }
11531 }
11532
11533 /* Load the nth game from open file f */
11534 int
11535 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11536 {
11537     ChessMove cm;
11538     char buf[MSG_SIZ];
11539     int gn = gameNumber;
11540     ListGame *lg = NULL;
11541     int numPGNTags = 0;
11542     int err, pos = -1;
11543     GameMode oldGameMode;
11544     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11545
11546     if (appData.debugMode)
11547         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11548
11549     if (gameMode == Training )
11550         SetTrainingModeOff();
11551
11552     oldGameMode = gameMode;
11553     if (gameMode != BeginningOfGame) {
11554       Reset(FALSE, TRUE);
11555     }
11556
11557     gameFileFP = f;
11558     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11559         fclose(lastLoadGameFP);
11560     }
11561
11562     if (useList) {
11563         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11564
11565         if (lg) {
11566             fseek(f, lg->offset, 0);
11567             GameListHighlight(gameNumber);
11568             pos = lg->position;
11569             gn = 1;
11570         }
11571         else {
11572             DisplayError(_("Game number out of range"), 0);
11573             return FALSE;
11574         }
11575     } else {
11576         GameListDestroy();
11577         if (fseek(f, 0, 0) == -1) {
11578             if (f == lastLoadGameFP ?
11579                 gameNumber == lastLoadGameNumber + 1 :
11580                 gameNumber == 1) {
11581                 gn = 1;
11582             } else {
11583                 DisplayError(_("Can't seek on game file"), 0);
11584                 return FALSE;
11585             }
11586         }
11587     }
11588     lastLoadGameFP = f;
11589     lastLoadGameNumber = gameNumber;
11590     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11591     lastLoadGameUseList = useList;
11592
11593     yynewfile(f);
11594
11595     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11596       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11597                 lg->gameInfo.black);
11598             DisplayTitle(buf);
11599     } else if (*title != NULLCHAR) {
11600         if (gameNumber > 1) {
11601           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11602             DisplayTitle(buf);
11603         } else {
11604             DisplayTitle(title);
11605         }
11606     }
11607
11608     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11609         gameMode = PlayFromGameFile;
11610         ModeHighlight();
11611     }
11612
11613     currentMove = forwardMostMove = backwardMostMove = 0;
11614     CopyBoard(boards[0], initialPosition);
11615     StopClocks();
11616
11617     /*
11618      * Skip the first gn-1 games in the file.
11619      * Also skip over anything that precedes an identifiable
11620      * start of game marker, to avoid being confused by
11621      * garbage at the start of the file.  Currently
11622      * recognized start of game markers are the move number "1",
11623      * the pattern "gnuchess .* game", the pattern
11624      * "^[#;%] [^ ]* game file", and a PGN tag block.
11625      * A game that starts with one of the latter two patterns
11626      * will also have a move number 1, possibly
11627      * following a position diagram.
11628      * 5-4-02: Let's try being more lenient and allowing a game to
11629      * start with an unnumbered move.  Does that break anything?
11630      */
11631     cm = lastLoadGameStart = EndOfFile;
11632     while (gn > 0) {
11633         yyboardindex = forwardMostMove;
11634         cm = (ChessMove) Myylex();
11635         switch (cm) {
11636           case EndOfFile:
11637             if (cmailMsgLoaded) {
11638                 nCmailGames = CMAIL_MAX_GAMES - gn;
11639             } else {
11640                 Reset(TRUE, TRUE);
11641                 DisplayError(_("Game not found in file"), 0);
11642             }
11643             return FALSE;
11644
11645           case GNUChessGame:
11646           case XBoardGame:
11647             gn--;
11648             lastLoadGameStart = cm;
11649             break;
11650
11651           case MoveNumberOne:
11652             switch (lastLoadGameStart) {
11653               case GNUChessGame:
11654               case XBoardGame:
11655               case PGNTag:
11656                 break;
11657               case MoveNumberOne:
11658               case EndOfFile:
11659                 gn--;           /* count this game */
11660                 lastLoadGameStart = cm;
11661                 break;
11662               default:
11663                 /* impossible */
11664                 break;
11665             }
11666             break;
11667
11668           case PGNTag:
11669             switch (lastLoadGameStart) {
11670               case GNUChessGame:
11671               case PGNTag:
11672               case MoveNumberOne:
11673               case EndOfFile:
11674                 gn--;           /* count this game */
11675                 lastLoadGameStart = cm;
11676                 break;
11677               case XBoardGame:
11678                 lastLoadGameStart = cm; /* game counted already */
11679                 break;
11680               default:
11681                 /* impossible */
11682                 break;
11683             }
11684             if (gn > 0) {
11685                 do {
11686                     yyboardindex = forwardMostMove;
11687                     cm = (ChessMove) Myylex();
11688                 } while (cm == PGNTag || cm == Comment);
11689             }
11690             break;
11691
11692           case WhiteWins:
11693           case BlackWins:
11694           case GameIsDrawn:
11695             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11696                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11697                     != CMAIL_OLD_RESULT) {
11698                     nCmailResults ++ ;
11699                     cmailResult[  CMAIL_MAX_GAMES
11700                                 - gn - 1] = CMAIL_OLD_RESULT;
11701                 }
11702             }
11703             break;
11704
11705           case NormalMove:
11706             /* Only a NormalMove can be at the start of a game
11707              * without a position diagram. */
11708             if (lastLoadGameStart == EndOfFile ) {
11709               gn--;
11710               lastLoadGameStart = MoveNumberOne;
11711             }
11712             break;
11713
11714           default:
11715             break;
11716         }
11717     }
11718
11719     if (appData.debugMode)
11720       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11721
11722     if (cm == XBoardGame) {
11723         /* Skip any header junk before position diagram and/or move 1 */
11724         for (;;) {
11725             yyboardindex = forwardMostMove;
11726             cm = (ChessMove) Myylex();
11727
11728             if (cm == EndOfFile ||
11729                 cm == GNUChessGame || cm == XBoardGame) {
11730                 /* Empty game; pretend end-of-file and handle later */
11731                 cm = EndOfFile;
11732                 break;
11733             }
11734
11735             if (cm == MoveNumberOne || cm == PositionDiagram ||
11736                 cm == PGNTag || cm == Comment)
11737               break;
11738         }
11739     } else if (cm == GNUChessGame) {
11740         if (gameInfo.event != NULL) {
11741             free(gameInfo.event);
11742         }
11743         gameInfo.event = StrSave(yy_text);
11744     }
11745
11746     startedFromSetupPosition = FALSE;
11747     while (cm == PGNTag) {
11748         if (appData.debugMode)
11749           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11750         err = ParsePGNTag(yy_text, &gameInfo);
11751         if (!err) numPGNTags++;
11752
11753         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11754         if(gameInfo.variant != oldVariant) {
11755             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11756             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11757             InitPosition(TRUE);
11758             oldVariant = gameInfo.variant;
11759             if (appData.debugMode)
11760               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11761         }
11762
11763
11764         if (gameInfo.fen != NULL) {
11765           Board initial_position;
11766           startedFromSetupPosition = TRUE;
11767           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11768             Reset(TRUE, TRUE);
11769             DisplayError(_("Bad FEN position in file"), 0);
11770             return FALSE;
11771           }
11772           CopyBoard(boards[0], initial_position);
11773           if (blackPlaysFirst) {
11774             currentMove = forwardMostMove = backwardMostMove = 1;
11775             CopyBoard(boards[1], initial_position);
11776             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11777             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11778             timeRemaining[0][1] = whiteTimeRemaining;
11779             timeRemaining[1][1] = blackTimeRemaining;
11780             if (commentList[0] != NULL) {
11781               commentList[1] = commentList[0];
11782               commentList[0] = NULL;
11783             }
11784           } else {
11785             currentMove = forwardMostMove = backwardMostMove = 0;
11786           }
11787           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11788           {   int i;
11789               initialRulePlies = FENrulePlies;
11790               for( i=0; i< nrCastlingRights; i++ )
11791                   initialRights[i] = initial_position[CASTLING][i];
11792           }
11793           yyboardindex = forwardMostMove;
11794           free(gameInfo.fen);
11795           gameInfo.fen = NULL;
11796         }
11797
11798         yyboardindex = forwardMostMove;
11799         cm = (ChessMove) Myylex();
11800
11801         /* Handle comments interspersed among the tags */
11802         while (cm == Comment) {
11803             char *p;
11804             if (appData.debugMode)
11805               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11806             p = yy_text;
11807             AppendComment(currentMove, p, FALSE);
11808             yyboardindex = forwardMostMove;
11809             cm = (ChessMove) Myylex();
11810         }
11811     }
11812
11813     /* don't rely on existence of Event tag since if game was
11814      * pasted from clipboard the Event tag may not exist
11815      */
11816     if (numPGNTags > 0){
11817         char *tags;
11818         if (gameInfo.variant == VariantNormal) {
11819           VariantClass v = StringToVariant(gameInfo.event);
11820           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11821           if(v < VariantShogi) gameInfo.variant = v;
11822         }
11823         if (!matchMode) {
11824           if( appData.autoDisplayTags ) {
11825             tags = PGNTags(&gameInfo);
11826             TagsPopUp(tags, CmailMsg());
11827             free(tags);
11828           }
11829         }
11830     } else {
11831         /* Make something up, but don't display it now */
11832         SetGameInfo();
11833         TagsPopDown();
11834     }
11835
11836     if (cm == PositionDiagram) {
11837         int i, j;
11838         char *p;
11839         Board initial_position;
11840
11841         if (appData.debugMode)
11842           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11843
11844         if (!startedFromSetupPosition) {
11845             p = yy_text;
11846             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11847               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11848                 switch (*p) {
11849                   case '{':
11850                   case '[':
11851                   case '-':
11852                   case ' ':
11853                   case '\t':
11854                   case '\n':
11855                   case '\r':
11856                     break;
11857                   default:
11858                     initial_position[i][j++] = CharToPiece(*p);
11859                     break;
11860                 }
11861             while (*p == ' ' || *p == '\t' ||
11862                    *p == '\n' || *p == '\r') p++;
11863
11864             if (strncmp(p, "black", strlen("black"))==0)
11865               blackPlaysFirst = TRUE;
11866             else
11867               blackPlaysFirst = FALSE;
11868             startedFromSetupPosition = TRUE;
11869
11870             CopyBoard(boards[0], initial_position);
11871             if (blackPlaysFirst) {
11872                 currentMove = forwardMostMove = backwardMostMove = 1;
11873                 CopyBoard(boards[1], initial_position);
11874                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11875                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11876                 timeRemaining[0][1] = whiteTimeRemaining;
11877                 timeRemaining[1][1] = blackTimeRemaining;
11878                 if (commentList[0] != NULL) {
11879                     commentList[1] = commentList[0];
11880                     commentList[0] = NULL;
11881                 }
11882             } else {
11883                 currentMove = forwardMostMove = backwardMostMove = 0;
11884             }
11885         }
11886         yyboardindex = forwardMostMove;
11887         cm = (ChessMove) Myylex();
11888     }
11889
11890     if (first.pr == NoProc) {
11891         StartChessProgram(&first);
11892     }
11893     InitChessProgram(&first, FALSE);
11894     SendToProgram("force\n", &first);
11895     if (startedFromSetupPosition) {
11896         SendBoard(&first, forwardMostMove);
11897     if (appData.debugMode) {
11898         fprintf(debugFP, "Load Game\n");
11899     }
11900         DisplayBothClocks();
11901     }
11902
11903     /* [HGM] server: flag to write setup moves in broadcast file as one */
11904     loadFlag = appData.suppressLoadMoves;
11905
11906     while (cm == Comment) {
11907         char *p;
11908         if (appData.debugMode)
11909           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11910         p = yy_text;
11911         AppendComment(currentMove, p, FALSE);
11912         yyboardindex = forwardMostMove;
11913         cm = (ChessMove) Myylex();
11914     }
11915
11916     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11917         cm == WhiteWins || cm == BlackWins ||
11918         cm == GameIsDrawn || cm == GameUnfinished) {
11919         DisplayMessage("", _("No moves in game"));
11920         if (cmailMsgLoaded) {
11921             if (appData.debugMode)
11922               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11923             ClearHighlights();
11924             flipView = FALSE;
11925         }
11926         DrawPosition(FALSE, boards[currentMove]);
11927         DisplayBothClocks();
11928         gameMode = EditGame;
11929         ModeHighlight();
11930         gameFileFP = NULL;
11931         cmailOldMove = 0;
11932         return TRUE;
11933     }
11934
11935     // [HGM] PV info: routine tests if comment empty
11936     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11937         DisplayComment(currentMove - 1, commentList[currentMove]);
11938     }
11939     if (!matchMode && appData.timeDelay != 0)
11940       DrawPosition(FALSE, boards[currentMove]);
11941
11942     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11943       programStats.ok_to_send = 1;
11944     }
11945
11946     /* if the first token after the PGN tags is a move
11947      * and not move number 1, retrieve it from the parser
11948      */
11949     if (cm != MoveNumberOne)
11950         LoadGameOneMove(cm);
11951
11952     /* load the remaining moves from the file */
11953     while (LoadGameOneMove(EndOfFile)) {
11954       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11955       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11956     }
11957
11958     /* rewind to the start of the game */
11959     currentMove = backwardMostMove;
11960
11961     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11962
11963     if (oldGameMode == AnalyzeFile ||
11964         oldGameMode == AnalyzeMode) {
11965       AnalyzeFileEvent();
11966     }
11967
11968     if (!matchMode && pos >= 0) {
11969         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11970     } else
11971     if (matchMode || appData.timeDelay == 0) {
11972       ToEndEvent();
11973     } else if (appData.timeDelay > 0) {
11974       AutoPlayGameLoop();
11975     }
11976
11977     if (appData.debugMode)
11978         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11979
11980     loadFlag = 0; /* [HGM] true game starts */
11981     return TRUE;
11982 }
11983
11984 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11985 int
11986 ReloadPosition (int offset)
11987 {
11988     int positionNumber = lastLoadPositionNumber + offset;
11989     if (lastLoadPositionFP == NULL) {
11990         DisplayError(_("No position has been loaded yet"), 0);
11991         return FALSE;
11992     }
11993     if (positionNumber <= 0) {
11994         DisplayError(_("Can't back up any further"), 0);
11995         return FALSE;
11996     }
11997     return LoadPosition(lastLoadPositionFP, positionNumber,
11998                         lastLoadPositionTitle);
11999 }
12000
12001 /* Load the nth position from the given file */
12002 int
12003 LoadPositionFromFile (char *filename, int n, char *title)
12004 {
12005     FILE *f;
12006     char buf[MSG_SIZ];
12007
12008     if (strcmp(filename, "-") == 0) {
12009         return LoadPosition(stdin, n, "stdin");
12010     } else {
12011         f = fopen(filename, "rb");
12012         if (f == NULL) {
12013             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12014             DisplayError(buf, errno);
12015             return FALSE;
12016         } else {
12017             return LoadPosition(f, n, title);
12018         }
12019     }
12020 }
12021
12022 /* Load the nth position from the given open file, and close it */
12023 int
12024 LoadPosition (FILE *f, int positionNumber, char *title)
12025 {
12026     char *p, line[MSG_SIZ];
12027     Board initial_position;
12028     int i, j, fenMode, pn;
12029
12030     if (gameMode == Training )
12031         SetTrainingModeOff();
12032
12033     if (gameMode != BeginningOfGame) {
12034         Reset(FALSE, TRUE);
12035     }
12036     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12037         fclose(lastLoadPositionFP);
12038     }
12039     if (positionNumber == 0) positionNumber = 1;
12040     lastLoadPositionFP = f;
12041     lastLoadPositionNumber = positionNumber;
12042     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12043     if (first.pr == NoProc && !appData.noChessProgram) {
12044       StartChessProgram(&first);
12045       InitChessProgram(&first, FALSE);
12046     }
12047     pn = positionNumber;
12048     if (positionNumber < 0) {
12049         /* Negative position number means to seek to that byte offset */
12050         if (fseek(f, -positionNumber, 0) == -1) {
12051             DisplayError(_("Can't seek on position file"), 0);
12052             return FALSE;
12053         };
12054         pn = 1;
12055     } else {
12056         if (fseek(f, 0, 0) == -1) {
12057             if (f == lastLoadPositionFP ?
12058                 positionNumber == lastLoadPositionNumber + 1 :
12059                 positionNumber == 1) {
12060                 pn = 1;
12061             } else {
12062                 DisplayError(_("Can't seek on position file"), 0);
12063                 return FALSE;
12064             }
12065         }
12066     }
12067     /* See if this file is FEN or old-style xboard */
12068     if (fgets(line, MSG_SIZ, f) == NULL) {
12069         DisplayError(_("Position not found in file"), 0);
12070         return FALSE;
12071     }
12072     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12073     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12074
12075     if (pn >= 2) {
12076         if (fenMode || line[0] == '#') pn--;
12077         while (pn > 0) {
12078             /* skip positions before number pn */
12079             if (fgets(line, MSG_SIZ, f) == NULL) {
12080                 Reset(TRUE, TRUE);
12081                 DisplayError(_("Position not found in file"), 0);
12082                 return FALSE;
12083             }
12084             if (fenMode || line[0] == '#') pn--;
12085         }
12086     }
12087
12088     if (fenMode) {
12089         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12090             DisplayError(_("Bad FEN position in file"), 0);
12091             return FALSE;
12092         }
12093     } else {
12094         (void) fgets(line, MSG_SIZ, f);
12095         (void) fgets(line, MSG_SIZ, f);
12096
12097         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12098             (void) fgets(line, MSG_SIZ, f);
12099             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12100                 if (*p == ' ')
12101                   continue;
12102                 initial_position[i][j++] = CharToPiece(*p);
12103             }
12104         }
12105
12106         blackPlaysFirst = FALSE;
12107         if (!feof(f)) {
12108             (void) fgets(line, MSG_SIZ, f);
12109             if (strncmp(line, "black", strlen("black"))==0)
12110               blackPlaysFirst = TRUE;
12111         }
12112     }
12113     startedFromSetupPosition = TRUE;
12114
12115     CopyBoard(boards[0], initial_position);
12116     if (blackPlaysFirst) {
12117         currentMove = forwardMostMove = backwardMostMove = 1;
12118         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12119         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12120         CopyBoard(boards[1], initial_position);
12121         DisplayMessage("", _("Black to play"));
12122     } else {
12123         currentMove = forwardMostMove = backwardMostMove = 0;
12124         DisplayMessage("", _("White to play"));
12125     }
12126     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12127     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12128         SendToProgram("force\n", &first);
12129         SendBoard(&first, forwardMostMove);
12130     }
12131     if (appData.debugMode) {
12132 int i, j;
12133   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12134   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12135         fprintf(debugFP, "Load Position\n");
12136     }
12137
12138     if (positionNumber > 1) {
12139       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12140         DisplayTitle(line);
12141     } else {
12142         DisplayTitle(title);
12143     }
12144     gameMode = EditGame;
12145     ModeHighlight();
12146     ResetClocks();
12147     timeRemaining[0][1] = whiteTimeRemaining;
12148     timeRemaining[1][1] = blackTimeRemaining;
12149     DrawPosition(FALSE, boards[currentMove]);
12150
12151     return TRUE;
12152 }
12153
12154
12155 void
12156 CopyPlayerNameIntoFileName (char **dest, char *src)
12157 {
12158     while (*src != NULLCHAR && *src != ',') {
12159         if (*src == ' ') {
12160             *(*dest)++ = '_';
12161             src++;
12162         } else {
12163             *(*dest)++ = *src++;
12164         }
12165     }
12166 }
12167
12168 char *
12169 DefaultFileName (char *ext)
12170 {
12171     static char def[MSG_SIZ];
12172     char *p;
12173
12174     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12175         p = def;
12176         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12177         *p++ = '-';
12178         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12179         *p++ = '.';
12180         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12181     } else {
12182         def[0] = NULLCHAR;
12183     }
12184     return def;
12185 }
12186
12187 /* Save the current game to the given file */
12188 int
12189 SaveGameToFile (char *filename, int append)
12190 {
12191     FILE *f;
12192     char buf[MSG_SIZ];
12193     int result, i, t,tot=0;
12194
12195     if (strcmp(filename, "-") == 0) {
12196         return SaveGame(stdout, 0, NULL);
12197     } else {
12198         for(i=0; i<10; i++) { // upto 10 tries
12199              f = fopen(filename, append ? "a" : "w");
12200              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12201              if(f || errno != 13) break;
12202              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12203              tot += t;
12204         }
12205         if (f == NULL) {
12206             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12207             DisplayError(buf, errno);
12208             return FALSE;
12209         } else {
12210             safeStrCpy(buf, lastMsg, MSG_SIZ);
12211             DisplayMessage(_("Waiting for access to save file"), "");
12212             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12213             DisplayMessage(_("Saving game"), "");
12214             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12215             result = SaveGame(f, 0, NULL);
12216             DisplayMessage(buf, "");
12217             return result;
12218         }
12219     }
12220 }
12221
12222 char *
12223 SavePart (char *str)
12224 {
12225     static char buf[MSG_SIZ];
12226     char *p;
12227
12228     p = strchr(str, ' ');
12229     if (p == NULL) return str;
12230     strncpy(buf, str, p - str);
12231     buf[p - str] = NULLCHAR;
12232     return buf;
12233 }
12234
12235 #define PGN_MAX_LINE 75
12236
12237 #define PGN_SIDE_WHITE  0
12238 #define PGN_SIDE_BLACK  1
12239
12240 static int
12241 FindFirstMoveOutOfBook (int side)
12242 {
12243     int result = -1;
12244
12245     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12246         int index = backwardMostMove;
12247         int has_book_hit = 0;
12248
12249         if( (index % 2) != side ) {
12250             index++;
12251         }
12252
12253         while( index < forwardMostMove ) {
12254             /* Check to see if engine is in book */
12255             int depth = pvInfoList[index].depth;
12256             int score = pvInfoList[index].score;
12257             int in_book = 0;
12258
12259             if( depth <= 2 ) {
12260                 in_book = 1;
12261             }
12262             else if( score == 0 && depth == 63 ) {
12263                 in_book = 1; /* Zappa */
12264             }
12265             else if( score == 2 && depth == 99 ) {
12266                 in_book = 1; /* Abrok */
12267             }
12268
12269             has_book_hit += in_book;
12270
12271             if( ! in_book ) {
12272                 result = index;
12273
12274                 break;
12275             }
12276
12277             index += 2;
12278         }
12279     }
12280
12281     return result;
12282 }
12283
12284 void
12285 GetOutOfBookInfo (char * buf)
12286 {
12287     int oob[2];
12288     int i;
12289     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12290
12291     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12292     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12293
12294     *buf = '\0';
12295
12296     if( oob[0] >= 0 || oob[1] >= 0 ) {
12297         for( i=0; i<2; i++ ) {
12298             int idx = oob[i];
12299
12300             if( idx >= 0 ) {
12301                 if( i > 0 && oob[0] >= 0 ) {
12302                     strcat( buf, "   " );
12303                 }
12304
12305                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12306                 sprintf( buf+strlen(buf), "%s%.2f",
12307                     pvInfoList[idx].score >= 0 ? "+" : "",
12308                     pvInfoList[idx].score / 100.0 );
12309             }
12310         }
12311     }
12312 }
12313
12314 /* Save game in PGN style and close the file */
12315 int
12316 SaveGamePGN (FILE *f)
12317 {
12318     int i, offset, linelen, newblock;
12319     time_t tm;
12320 //    char *movetext;
12321     char numtext[32];
12322     int movelen, numlen, blank;
12323     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12324
12325     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12326
12327     tm = time((time_t *) NULL);
12328
12329     PrintPGNTags(f, &gameInfo);
12330
12331     if (backwardMostMove > 0 || startedFromSetupPosition) {
12332         char *fen = PositionToFEN(backwardMostMove, NULL);
12333         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12334         fprintf(f, "\n{--------------\n");
12335         PrintPosition(f, backwardMostMove);
12336         fprintf(f, "--------------}\n");
12337         free(fen);
12338     }
12339     else {
12340         /* [AS] Out of book annotation */
12341         if( appData.saveOutOfBookInfo ) {
12342             char buf[64];
12343
12344             GetOutOfBookInfo( buf );
12345
12346             if( buf[0] != '\0' ) {
12347                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12348             }
12349         }
12350
12351         fprintf(f, "\n");
12352     }
12353
12354     i = backwardMostMove;
12355     linelen = 0;
12356     newblock = TRUE;
12357
12358     while (i < forwardMostMove) {
12359         /* Print comments preceding this move */
12360         if (commentList[i] != NULL) {
12361             if (linelen > 0) fprintf(f, "\n");
12362             fprintf(f, "%s", commentList[i]);
12363             linelen = 0;
12364             newblock = TRUE;
12365         }
12366
12367         /* Format move number */
12368         if ((i % 2) == 0)
12369           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12370         else
12371           if (newblock)
12372             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12373           else
12374             numtext[0] = NULLCHAR;
12375
12376         numlen = strlen(numtext);
12377         newblock = FALSE;
12378
12379         /* Print move number */
12380         blank = linelen > 0 && numlen > 0;
12381         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12382             fprintf(f, "\n");
12383             linelen = 0;
12384             blank = 0;
12385         }
12386         if (blank) {
12387             fprintf(f, " ");
12388             linelen++;
12389         }
12390         fprintf(f, "%s", numtext);
12391         linelen += numlen;
12392
12393         /* Get move */
12394         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12395         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12396
12397         /* Print move */
12398         blank = linelen > 0 && movelen > 0;
12399         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12400             fprintf(f, "\n");
12401             linelen = 0;
12402             blank = 0;
12403         }
12404         if (blank) {
12405             fprintf(f, " ");
12406             linelen++;
12407         }
12408         fprintf(f, "%s", move_buffer);
12409         linelen += movelen;
12410
12411         /* [AS] Add PV info if present */
12412         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12413             /* [HGM] add time */
12414             char buf[MSG_SIZ]; int seconds;
12415
12416             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12417
12418             if( seconds <= 0)
12419               buf[0] = 0;
12420             else
12421               if( seconds < 30 )
12422                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12423               else
12424                 {
12425                   seconds = (seconds + 4)/10; // round to full seconds
12426                   if( seconds < 60 )
12427                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12428                   else
12429                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12430                 }
12431
12432             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12433                       pvInfoList[i].score >= 0 ? "+" : "",
12434                       pvInfoList[i].score / 100.0,
12435                       pvInfoList[i].depth,
12436                       buf );
12437
12438             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12439
12440             /* Print score/depth */
12441             blank = linelen > 0 && movelen > 0;
12442             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12443                 fprintf(f, "\n");
12444                 linelen = 0;
12445                 blank = 0;
12446             }
12447             if (blank) {
12448                 fprintf(f, " ");
12449                 linelen++;
12450             }
12451             fprintf(f, "%s", move_buffer);
12452             linelen += movelen;
12453         }
12454
12455         i++;
12456     }
12457
12458     /* Start a new line */
12459     if (linelen > 0) fprintf(f, "\n");
12460
12461     /* Print comments after last move */
12462     if (commentList[i] != NULL) {
12463         fprintf(f, "%s\n", commentList[i]);
12464     }
12465
12466     /* Print result */
12467     if (gameInfo.resultDetails != NULL &&
12468         gameInfo.resultDetails[0] != NULLCHAR) {
12469         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12470                 PGNResult(gameInfo.result));
12471     } else {
12472         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12473     }
12474
12475     fclose(f);
12476     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12477     return TRUE;
12478 }
12479
12480 /* Save game in old style and close the file */
12481 int
12482 SaveGameOldStyle (FILE *f)
12483 {
12484     int i, offset;
12485     time_t tm;
12486
12487     tm = time((time_t *) NULL);
12488
12489     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12490     PrintOpponents(f);
12491
12492     if (backwardMostMove > 0 || startedFromSetupPosition) {
12493         fprintf(f, "\n[--------------\n");
12494         PrintPosition(f, backwardMostMove);
12495         fprintf(f, "--------------]\n");
12496     } else {
12497         fprintf(f, "\n");
12498     }
12499
12500     i = backwardMostMove;
12501     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12502
12503     while (i < forwardMostMove) {
12504         if (commentList[i] != NULL) {
12505             fprintf(f, "[%s]\n", commentList[i]);
12506         }
12507
12508         if ((i % 2) == 1) {
12509             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12510             i++;
12511         } else {
12512             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12513             i++;
12514             if (commentList[i] != NULL) {
12515                 fprintf(f, "\n");
12516                 continue;
12517             }
12518             if (i >= forwardMostMove) {
12519                 fprintf(f, "\n");
12520                 break;
12521             }
12522             fprintf(f, "%s\n", parseList[i]);
12523             i++;
12524         }
12525     }
12526
12527     if (commentList[i] != NULL) {
12528         fprintf(f, "[%s]\n", commentList[i]);
12529     }
12530
12531     /* This isn't really the old style, but it's close enough */
12532     if (gameInfo.resultDetails != NULL &&
12533         gameInfo.resultDetails[0] != NULLCHAR) {
12534         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12535                 gameInfo.resultDetails);
12536     } else {
12537         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12538     }
12539
12540     fclose(f);
12541     return TRUE;
12542 }
12543
12544 /* Save the current game to open file f and close the file */
12545 int
12546 SaveGame (FILE *f, int dummy, char *dummy2)
12547 {
12548     if (gameMode == EditPosition) EditPositionDone(TRUE);
12549     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12550     if (appData.oldSaveStyle)
12551       return SaveGameOldStyle(f);
12552     else
12553       return SaveGamePGN(f);
12554 }
12555
12556 /* Save the current position to the given file */
12557 int
12558 SavePositionToFile (char *filename)
12559 {
12560     FILE *f;
12561     char buf[MSG_SIZ];
12562
12563     if (strcmp(filename, "-") == 0) {
12564         return SavePosition(stdout, 0, NULL);
12565     } else {
12566         f = fopen(filename, "a");
12567         if (f == NULL) {
12568             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12569             DisplayError(buf, errno);
12570             return FALSE;
12571         } else {
12572             safeStrCpy(buf, lastMsg, MSG_SIZ);
12573             DisplayMessage(_("Waiting for access to save file"), "");
12574             flock(fileno(f), LOCK_EX); // [HGM] lock
12575             DisplayMessage(_("Saving position"), "");
12576             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12577             SavePosition(f, 0, NULL);
12578             DisplayMessage(buf, "");
12579             return TRUE;
12580         }
12581     }
12582 }
12583
12584 /* Save the current position to the given open file and close the file */
12585 int
12586 SavePosition (FILE *f, int dummy, char *dummy2)
12587 {
12588     time_t tm;
12589     char *fen;
12590
12591     if (gameMode == EditPosition) EditPositionDone(TRUE);
12592     if (appData.oldSaveStyle) {
12593         tm = time((time_t *) NULL);
12594
12595         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12596         PrintOpponents(f);
12597         fprintf(f, "[--------------\n");
12598         PrintPosition(f, currentMove);
12599         fprintf(f, "--------------]\n");
12600     } else {
12601         fen = PositionToFEN(currentMove, NULL);
12602         fprintf(f, "%s\n", fen);
12603         free(fen);
12604     }
12605     fclose(f);
12606     return TRUE;
12607 }
12608
12609 void
12610 ReloadCmailMsgEvent (int unregister)
12611 {
12612 #if !WIN32
12613     static char *inFilename = NULL;
12614     static char *outFilename;
12615     int i;
12616     struct stat inbuf, outbuf;
12617     int status;
12618
12619     /* Any registered moves are unregistered if unregister is set, */
12620     /* i.e. invoked by the signal handler */
12621     if (unregister) {
12622         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12623             cmailMoveRegistered[i] = FALSE;
12624             if (cmailCommentList[i] != NULL) {
12625                 free(cmailCommentList[i]);
12626                 cmailCommentList[i] = NULL;
12627             }
12628         }
12629         nCmailMovesRegistered = 0;
12630     }
12631
12632     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12633         cmailResult[i] = CMAIL_NOT_RESULT;
12634     }
12635     nCmailResults = 0;
12636
12637     if (inFilename == NULL) {
12638         /* Because the filenames are static they only get malloced once  */
12639         /* and they never get freed                                      */
12640         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12641         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12642
12643         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12644         sprintf(outFilename, "%s.out", appData.cmailGameName);
12645     }
12646
12647     status = stat(outFilename, &outbuf);
12648     if (status < 0) {
12649         cmailMailedMove = FALSE;
12650     } else {
12651         status = stat(inFilename, &inbuf);
12652         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12653     }
12654
12655     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12656        counts the games, notes how each one terminated, etc.
12657
12658        It would be nice to remove this kludge and instead gather all
12659        the information while building the game list.  (And to keep it
12660        in the game list nodes instead of having a bunch of fixed-size
12661        parallel arrays.)  Note this will require getting each game's
12662        termination from the PGN tags, as the game list builder does
12663        not process the game moves.  --mann
12664        */
12665     cmailMsgLoaded = TRUE;
12666     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12667
12668     /* Load first game in the file or popup game menu */
12669     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12670
12671 #endif /* !WIN32 */
12672     return;
12673 }
12674
12675 int
12676 RegisterMove ()
12677 {
12678     FILE *f;
12679     char string[MSG_SIZ];
12680
12681     if (   cmailMailedMove
12682         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12683         return TRUE;            /* Allow free viewing  */
12684     }
12685
12686     /* Unregister move to ensure that we don't leave RegisterMove        */
12687     /* with the move registered when the conditions for registering no   */
12688     /* longer hold                                                       */
12689     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12690         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12691         nCmailMovesRegistered --;
12692
12693         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12694           {
12695               free(cmailCommentList[lastLoadGameNumber - 1]);
12696               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12697           }
12698     }
12699
12700     if (cmailOldMove == -1) {
12701         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12702         return FALSE;
12703     }
12704
12705     if (currentMove > cmailOldMove + 1) {
12706         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12707         return FALSE;
12708     }
12709
12710     if (currentMove < cmailOldMove) {
12711         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12712         return FALSE;
12713     }
12714
12715     if (forwardMostMove > currentMove) {
12716         /* Silently truncate extra moves */
12717         TruncateGame();
12718     }
12719
12720     if (   (currentMove == cmailOldMove + 1)
12721         || (   (currentMove == cmailOldMove)
12722             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12723                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12724         if (gameInfo.result != GameUnfinished) {
12725             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12726         }
12727
12728         if (commentList[currentMove] != NULL) {
12729             cmailCommentList[lastLoadGameNumber - 1]
12730               = StrSave(commentList[currentMove]);
12731         }
12732         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12733
12734         if (appData.debugMode)
12735           fprintf(debugFP, "Saving %s for game %d\n",
12736                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12737
12738         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12739
12740         f = fopen(string, "w");
12741         if (appData.oldSaveStyle) {
12742             SaveGameOldStyle(f); /* also closes the file */
12743
12744             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12745             f = fopen(string, "w");
12746             SavePosition(f, 0, NULL); /* also closes the file */
12747         } else {
12748             fprintf(f, "{--------------\n");
12749             PrintPosition(f, currentMove);
12750             fprintf(f, "--------------}\n\n");
12751
12752             SaveGame(f, 0, NULL); /* also closes the file*/
12753         }
12754
12755         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12756         nCmailMovesRegistered ++;
12757     } else if (nCmailGames == 1) {
12758         DisplayError(_("You have not made a move yet"), 0);
12759         return FALSE;
12760     }
12761
12762     return TRUE;
12763 }
12764
12765 void
12766 MailMoveEvent ()
12767 {
12768 #if !WIN32
12769     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12770     FILE *commandOutput;
12771     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12772     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12773     int nBuffers;
12774     int i;
12775     int archived;
12776     char *arcDir;
12777
12778     if (! cmailMsgLoaded) {
12779         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12780         return;
12781     }
12782
12783     if (nCmailGames == nCmailResults) {
12784         DisplayError(_("No unfinished games"), 0);
12785         return;
12786     }
12787
12788 #if CMAIL_PROHIBIT_REMAIL
12789     if (cmailMailedMove) {
12790       snprintf(msg, MSG_SIZ, _("You have already mailed a move.\nWait until a move arrives from your opponent.\nTo resend the same move, type\n\"cmail -remail -game %s\"\non the command line."), appData.cmailGameName);
12791         DisplayError(msg, 0);
12792         return;
12793     }
12794 #endif
12795
12796     if (! (cmailMailedMove || RegisterMove())) return;
12797
12798     if (   cmailMailedMove
12799         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12800       snprintf(string, MSG_SIZ, partCommandString,
12801                appData.debugMode ? " -v" : "", appData.cmailGameName);
12802         commandOutput = popen(string, "r");
12803
12804         if (commandOutput == NULL) {
12805             DisplayError(_("Failed to invoke cmail"), 0);
12806         } else {
12807             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12808                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12809             }
12810             if (nBuffers > 1) {
12811                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12812                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12813                 nBytes = MSG_SIZ - 1;
12814             } else {
12815                 (void) memcpy(msg, buffer, nBytes);
12816             }
12817             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12818
12819             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12820                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12821
12822                 archived = TRUE;
12823                 for (i = 0; i < nCmailGames; i ++) {
12824                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12825                         archived = FALSE;
12826                     }
12827                 }
12828                 if (   archived
12829                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12830                         != NULL)) {
12831                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12832                            arcDir,
12833                            appData.cmailGameName,
12834                            gameInfo.date);
12835                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12836                     cmailMsgLoaded = FALSE;
12837                 }
12838             }
12839
12840             DisplayInformation(msg);
12841             pclose(commandOutput);
12842         }
12843     } else {
12844         if ((*cmailMsg) != '\0') {
12845             DisplayInformation(cmailMsg);
12846         }
12847     }
12848
12849     return;
12850 #endif /* !WIN32 */
12851 }
12852
12853 char *
12854 CmailMsg ()
12855 {
12856 #if WIN32
12857     return NULL;
12858 #else
12859     int  prependComma = 0;
12860     char number[5];
12861     char string[MSG_SIZ];       /* Space for game-list */
12862     int  i;
12863
12864     if (!cmailMsgLoaded) return "";
12865
12866     if (cmailMailedMove) {
12867       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12868     } else {
12869         /* Create a list of games left */
12870       snprintf(string, MSG_SIZ, "[");
12871         for (i = 0; i < nCmailGames; i ++) {
12872             if (! (   cmailMoveRegistered[i]
12873                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12874                 if (prependComma) {
12875                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12876                 } else {
12877                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12878                     prependComma = 1;
12879                 }
12880
12881                 strcat(string, number);
12882             }
12883         }
12884         strcat(string, "]");
12885
12886         if (nCmailMovesRegistered + nCmailResults == 0) {
12887             switch (nCmailGames) {
12888               case 1:
12889                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12890                 break;
12891
12892               case 2:
12893                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12894                 break;
12895
12896               default:
12897                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12898                          nCmailGames);
12899                 break;
12900             }
12901         } else {
12902             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12903               case 1:
12904                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12905                          string);
12906                 break;
12907
12908               case 0:
12909                 if (nCmailResults == nCmailGames) {
12910                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12911                 } else {
12912                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12913                 }
12914                 break;
12915
12916               default:
12917                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12918                          string);
12919             }
12920         }
12921     }
12922     return cmailMsg;
12923 #endif /* WIN32 */
12924 }
12925
12926 void
12927 ResetGameEvent ()
12928 {
12929     if (gameMode == Training)
12930       SetTrainingModeOff();
12931
12932     Reset(TRUE, TRUE);
12933     cmailMsgLoaded = FALSE;
12934     if (appData.icsActive) {
12935       SendToICS(ics_prefix);
12936       SendToICS("refresh\n");
12937     }
12938 }
12939
12940 void
12941 ExitEvent (int status)
12942 {
12943     exiting++;
12944     if (exiting > 2) {
12945       /* Give up on clean exit */
12946       exit(status);
12947     }
12948     if (exiting > 1) {
12949       /* Keep trying for clean exit */
12950       return;
12951     }
12952
12953     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12954
12955     if (telnetISR != NULL) {
12956       RemoveInputSource(telnetISR);
12957     }
12958     if (icsPR != NoProc) {
12959       DestroyChildProcess(icsPR, TRUE);
12960     }
12961
12962     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12963     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12964
12965     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12966     /* make sure this other one finishes before killing it!                  */
12967     if(endingGame) { int count = 0;
12968         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12969         while(endingGame && count++ < 10) DoSleep(1);
12970         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12971     }
12972
12973     /* Kill off chess programs */
12974     if (first.pr != NoProc) {
12975         ExitAnalyzeMode();
12976
12977         DoSleep( appData.delayBeforeQuit );
12978         SendToProgram("quit\n", &first);
12979         DoSleep( appData.delayAfterQuit );
12980         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12981     }
12982     if (second.pr != NoProc) {
12983         DoSleep( appData.delayBeforeQuit );
12984         SendToProgram("quit\n", &second);
12985         DoSleep( appData.delayAfterQuit );
12986         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12987     }
12988     if (first.isr != NULL) {
12989         RemoveInputSource(first.isr);
12990     }
12991     if (second.isr != NULL) {
12992         RemoveInputSource(second.isr);
12993     }
12994
12995     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12996     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12997
12998     ShutDownFrontEnd();
12999     exit(status);
13000 }
13001
13002 void
13003 PauseEvent ()
13004 {
13005     if (appData.debugMode)
13006         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13007     if (pausing) {
13008         pausing = FALSE;
13009         ModeHighlight();
13010         if (gameMode == MachinePlaysWhite ||
13011             gameMode == MachinePlaysBlack) {
13012             StartClocks();
13013         } else {
13014             DisplayBothClocks();
13015         }
13016         if (gameMode == PlayFromGameFile) {
13017             if (appData.timeDelay >= 0)
13018                 AutoPlayGameLoop();
13019         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13020             Reset(FALSE, TRUE);
13021             SendToICS(ics_prefix);
13022             SendToICS("refresh\n");
13023         } else if (currentMove < forwardMostMove) {
13024             ForwardInner(forwardMostMove);
13025         }
13026         pauseExamInvalid = FALSE;
13027     } else {
13028         switch (gameMode) {
13029           default:
13030             return;
13031           case IcsExamining:
13032             pauseExamForwardMostMove = forwardMostMove;
13033             pauseExamInvalid = FALSE;
13034             /* fall through */
13035           case IcsObserving:
13036           case IcsPlayingWhite:
13037           case IcsPlayingBlack:
13038             pausing = TRUE;
13039             ModeHighlight();
13040             return;
13041           case PlayFromGameFile:
13042             (void) StopLoadGameTimer();
13043             pausing = TRUE;
13044             ModeHighlight();
13045             break;
13046           case BeginningOfGame:
13047             if (appData.icsActive) return;
13048             /* else fall through */
13049           case MachinePlaysWhite:
13050           case MachinePlaysBlack:
13051           case TwoMachinesPlay:
13052             if (forwardMostMove == 0)
13053               return;           /* don't pause if no one has moved */
13054             if ((gameMode == MachinePlaysWhite &&
13055                  !WhiteOnMove(forwardMostMove)) ||
13056                 (gameMode == MachinePlaysBlack &&
13057                  WhiteOnMove(forwardMostMove))) {
13058                 StopClocks();
13059             }
13060             pausing = TRUE;
13061             ModeHighlight();
13062             break;
13063         }
13064     }
13065 }
13066
13067 void
13068 EditCommentEvent ()
13069 {
13070     char title[MSG_SIZ];
13071
13072     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13073       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13074     } else {
13075       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13076                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13077                parseList[currentMove - 1]);
13078     }
13079
13080     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13081 }
13082
13083
13084 void
13085 EditTagsEvent ()
13086 {
13087     char *tags = PGNTags(&gameInfo);
13088     bookUp = FALSE;
13089     EditTagsPopUp(tags, NULL);
13090     free(tags);
13091 }
13092
13093 void
13094 AnalyzeModeEvent ()
13095 {
13096     if (appData.noChessProgram || gameMode == AnalyzeMode)
13097       return;
13098
13099     if (gameMode != AnalyzeFile) {
13100         if (!appData.icsEngineAnalyze) {
13101                EditGameEvent();
13102                if (gameMode != EditGame) return;
13103         }
13104         ResurrectChessProgram();
13105         SendToProgram("analyze\n", &first);
13106         first.analyzing = TRUE;
13107         /*first.maybeThinking = TRUE;*/
13108         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13109         EngineOutputPopUp();
13110     }
13111     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13112     pausing = FALSE;
13113     ModeHighlight();
13114     SetGameInfo();
13115
13116     StartAnalysisClock();
13117     GetTimeMark(&lastNodeCountTime);
13118     lastNodeCount = 0;
13119 }
13120
13121 void
13122 AnalyzeFileEvent ()
13123 {
13124     if (appData.noChessProgram || gameMode == AnalyzeFile)
13125       return;
13126
13127     if (gameMode != AnalyzeMode) {
13128         EditGameEvent();
13129         if (gameMode != EditGame) return;
13130         ResurrectChessProgram();
13131         SendToProgram("analyze\n", &first);
13132         first.analyzing = TRUE;
13133         /*first.maybeThinking = TRUE;*/
13134         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13135         EngineOutputPopUp();
13136     }
13137     gameMode = AnalyzeFile;
13138     pausing = FALSE;
13139     ModeHighlight();
13140     SetGameInfo();
13141
13142     StartAnalysisClock();
13143     GetTimeMark(&lastNodeCountTime);
13144     lastNodeCount = 0;
13145     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13146 }
13147
13148 void
13149 MachineWhiteEvent ()
13150 {
13151     char buf[MSG_SIZ];
13152     char *bookHit = NULL;
13153
13154     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13155       return;
13156
13157
13158     if (gameMode == PlayFromGameFile ||
13159         gameMode == TwoMachinesPlay  ||
13160         gameMode == Training         ||
13161         gameMode == AnalyzeMode      ||
13162         gameMode == EndOfGame)
13163         EditGameEvent();
13164
13165     if (gameMode == EditPosition)
13166         EditPositionDone(TRUE);
13167
13168     if (!WhiteOnMove(currentMove)) {
13169         DisplayError(_("It is not White's turn"), 0);
13170         return;
13171     }
13172
13173     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13174       ExitAnalyzeMode();
13175
13176     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13177         gameMode == AnalyzeFile)
13178         TruncateGame();
13179
13180     ResurrectChessProgram();    /* in case it isn't running */
13181     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13182         gameMode = MachinePlaysWhite;
13183         ResetClocks();
13184     } else
13185     gameMode = MachinePlaysWhite;
13186     pausing = FALSE;
13187     ModeHighlight();
13188     SetGameInfo();
13189     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13190     DisplayTitle(buf);
13191     if (first.sendName) {
13192       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13193       SendToProgram(buf, &first);
13194     }
13195     if (first.sendTime) {
13196       if (first.useColors) {
13197         SendToProgram("black\n", &first); /*gnu kludge*/
13198       }
13199       SendTimeRemaining(&first, TRUE);
13200     }
13201     if (first.useColors) {
13202       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13203     }
13204     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13205     SetMachineThinkingEnables();
13206     first.maybeThinking = TRUE;
13207     StartClocks();
13208     firstMove = FALSE;
13209
13210     if (appData.autoFlipView && !flipView) {
13211       flipView = !flipView;
13212       DrawPosition(FALSE, NULL);
13213       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13214     }
13215
13216     if(bookHit) { // [HGM] book: simulate book reply
13217         static char bookMove[MSG_SIZ]; // a bit generous?
13218
13219         programStats.nodes = programStats.depth = programStats.time =
13220         programStats.score = programStats.got_only_move = 0;
13221         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13222
13223         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13224         strcat(bookMove, bookHit);
13225         HandleMachineMove(bookMove, &first);
13226     }
13227 }
13228
13229 void
13230 MachineBlackEvent ()
13231 {
13232   char buf[MSG_SIZ];
13233   char *bookHit = NULL;
13234
13235     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13236         return;
13237
13238
13239     if (gameMode == PlayFromGameFile ||
13240         gameMode == TwoMachinesPlay  ||
13241         gameMode == Training         ||
13242         gameMode == AnalyzeMode      ||
13243         gameMode == EndOfGame)
13244         EditGameEvent();
13245
13246     if (gameMode == EditPosition)
13247         EditPositionDone(TRUE);
13248
13249     if (WhiteOnMove(currentMove)) {
13250         DisplayError(_("It is not Black's turn"), 0);
13251         return;
13252     }
13253
13254     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13255       ExitAnalyzeMode();
13256
13257     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13258         gameMode == AnalyzeFile)
13259         TruncateGame();
13260
13261     ResurrectChessProgram();    /* in case it isn't running */
13262     gameMode = MachinePlaysBlack;
13263     pausing = FALSE;
13264     ModeHighlight();
13265     SetGameInfo();
13266     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13267     DisplayTitle(buf);
13268     if (first.sendName) {
13269       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13270       SendToProgram(buf, &first);
13271     }
13272     if (first.sendTime) {
13273       if (first.useColors) {
13274         SendToProgram("white\n", &first); /*gnu kludge*/
13275       }
13276       SendTimeRemaining(&first, FALSE);
13277     }
13278     if (first.useColors) {
13279       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13280     }
13281     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13282     SetMachineThinkingEnables();
13283     first.maybeThinking = TRUE;
13284     StartClocks();
13285
13286     if (appData.autoFlipView && flipView) {
13287       flipView = !flipView;
13288       DrawPosition(FALSE, NULL);
13289       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13290     }
13291     if(bookHit) { // [HGM] book: simulate book reply
13292         static char bookMove[MSG_SIZ]; // a bit generous?
13293
13294         programStats.nodes = programStats.depth = programStats.time =
13295         programStats.score = programStats.got_only_move = 0;
13296         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13297
13298         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13299         strcat(bookMove, bookHit);
13300         HandleMachineMove(bookMove, &first);
13301     }
13302 }
13303
13304
13305 void
13306 DisplayTwoMachinesTitle ()
13307 {
13308     char buf[MSG_SIZ];
13309     if (appData.matchGames > 0) {
13310         if(appData.tourneyFile[0]) {
13311           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13312                    gameInfo.white, _("vs."), gameInfo.black,
13313                    nextGame+1, appData.matchGames+1,
13314                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13315         } else 
13316         if (first.twoMachinesColor[0] == 'w') {
13317           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13318                    gameInfo.white, _("vs."),  gameInfo.black,
13319                    first.matchWins, second.matchWins,
13320                    matchGame - 1 - (first.matchWins + second.matchWins));
13321         } else {
13322           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13323                    gameInfo.white, _("vs."), gameInfo.black,
13324                    second.matchWins, first.matchWins,
13325                    matchGame - 1 - (first.matchWins + second.matchWins));
13326         }
13327     } else {
13328       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13329     }
13330     DisplayTitle(buf);
13331 }
13332
13333 void
13334 SettingsMenuIfReady ()
13335 {
13336   if (second.lastPing != second.lastPong) {
13337     DisplayMessage("", _("Waiting for second chess program"));
13338     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13339     return;
13340   }
13341   ThawUI();
13342   DisplayMessage("", "");
13343   SettingsPopUp(&second);
13344 }
13345
13346 int
13347 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13348 {
13349     char buf[MSG_SIZ];
13350     if (cps->pr == NoProc) {
13351         StartChessProgram(cps);
13352         if (cps->protocolVersion == 1) {
13353           retry();
13354         } else {
13355           /* kludge: allow timeout for initial "feature" command */
13356           FreezeUI();
13357           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13358           DisplayMessage("", buf);
13359           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13360         }
13361         return 1;
13362     }
13363     return 0;
13364 }
13365
13366 void
13367 TwoMachinesEvent P((void))
13368 {
13369     int i;
13370     char buf[MSG_SIZ];
13371     ChessProgramState *onmove;
13372     char *bookHit = NULL;
13373     static int stalling = 0;
13374     TimeMark now;
13375     long wait;
13376
13377     if (appData.noChessProgram) return;
13378
13379     switch (gameMode) {
13380       case TwoMachinesPlay:
13381         return;
13382       case MachinePlaysWhite:
13383       case MachinePlaysBlack:
13384         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13385             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13386             return;
13387         }
13388         /* fall through */
13389       case BeginningOfGame:
13390       case PlayFromGameFile:
13391       case EndOfGame:
13392         EditGameEvent();
13393         if (gameMode != EditGame) return;
13394         break;
13395       case EditPosition:
13396         EditPositionDone(TRUE);
13397         break;
13398       case AnalyzeMode:
13399       case AnalyzeFile:
13400         ExitAnalyzeMode();
13401         break;
13402       case EditGame:
13403       default:
13404         break;
13405     }
13406
13407 //    forwardMostMove = currentMove;
13408     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13409
13410     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13411
13412     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13413     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13414       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13415       return;
13416     }
13417     if(!stalling) {
13418       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13419       SendToProgram("force\n", &second);
13420       stalling = 1;
13421       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13422       return;
13423     }
13424     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13425     if(appData.matchPause>10000 || appData.matchPause<10)
13426                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13427     wait = SubtractTimeMarks(&now, &pauseStart);
13428     if(wait < appData.matchPause) {
13429         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13430         return;
13431     }
13432     stalling = 0;
13433     DisplayMessage("", "");
13434     if (startedFromSetupPosition) {
13435         SendBoard(&second, backwardMostMove);
13436     if (appData.debugMode) {
13437         fprintf(debugFP, "Two Machines\n");
13438     }
13439     }
13440     for (i = backwardMostMove; i < forwardMostMove; i++) {
13441         SendMoveToProgram(i, &second);
13442     }
13443
13444     gameMode = TwoMachinesPlay;
13445     pausing = FALSE;
13446     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13447     SetGameInfo();
13448     DisplayTwoMachinesTitle();
13449     firstMove = TRUE;
13450     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13451         onmove = &first;
13452     } else {
13453         onmove = &second;
13454     }
13455     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13456     SendToProgram(first.computerString, &first);
13457     if (first.sendName) {
13458       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13459       SendToProgram(buf, &first);
13460     }
13461     SendToProgram(second.computerString, &second);
13462     if (second.sendName) {
13463       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13464       SendToProgram(buf, &second);
13465     }
13466
13467     ResetClocks();
13468     if (!first.sendTime || !second.sendTime) {
13469         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13470         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13471     }
13472     if (onmove->sendTime) {
13473       if (onmove->useColors) {
13474         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13475       }
13476       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13477     }
13478     if (onmove->useColors) {
13479       SendToProgram(onmove->twoMachinesColor, onmove);
13480     }
13481     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13482 //    SendToProgram("go\n", onmove);
13483     onmove->maybeThinking = TRUE;
13484     SetMachineThinkingEnables();
13485
13486     StartClocks();
13487
13488     if(bookHit) { // [HGM] book: simulate book reply
13489         static char bookMove[MSG_SIZ]; // a bit generous?
13490
13491         programStats.nodes = programStats.depth = programStats.time =
13492         programStats.score = programStats.got_only_move = 0;
13493         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13494
13495         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13496         strcat(bookMove, bookHit);
13497         savedMessage = bookMove; // args for deferred call
13498         savedState = onmove;
13499         ScheduleDelayedEvent(DeferredBookMove, 1);
13500     }
13501 }
13502
13503 void
13504 TrainingEvent ()
13505 {
13506     if (gameMode == Training) {
13507       SetTrainingModeOff();
13508       gameMode = PlayFromGameFile;
13509       DisplayMessage("", _("Training mode off"));
13510     } else {
13511       gameMode = Training;
13512       animateTraining = appData.animate;
13513
13514       /* make sure we are not already at the end of the game */
13515       if (currentMove < forwardMostMove) {
13516         SetTrainingModeOn();
13517         DisplayMessage("", _("Training mode on"));
13518       } else {
13519         gameMode = PlayFromGameFile;
13520         DisplayError(_("Already at end of game"), 0);
13521       }
13522     }
13523     ModeHighlight();
13524 }
13525
13526 void
13527 IcsClientEvent ()
13528 {
13529     if (!appData.icsActive) return;
13530     switch (gameMode) {
13531       case IcsPlayingWhite:
13532       case IcsPlayingBlack:
13533       case IcsObserving:
13534       case IcsIdle:
13535       case BeginningOfGame:
13536       case IcsExamining:
13537         return;
13538
13539       case EditGame:
13540         break;
13541
13542       case EditPosition:
13543         EditPositionDone(TRUE);
13544         break;
13545
13546       case AnalyzeMode:
13547       case AnalyzeFile:
13548         ExitAnalyzeMode();
13549         break;
13550
13551       default:
13552         EditGameEvent();
13553         break;
13554     }
13555
13556     gameMode = IcsIdle;
13557     ModeHighlight();
13558     return;
13559 }
13560
13561 void
13562 EditGameEvent ()
13563 {
13564     int i;
13565
13566     switch (gameMode) {
13567       case Training:
13568         SetTrainingModeOff();
13569         break;
13570       case MachinePlaysWhite:
13571       case MachinePlaysBlack:
13572       case BeginningOfGame:
13573         SendToProgram("force\n", &first);
13574         SetUserThinkingEnables();
13575         break;
13576       case PlayFromGameFile:
13577         (void) StopLoadGameTimer();
13578         if (gameFileFP != NULL) {
13579             gameFileFP = NULL;
13580         }
13581         break;
13582       case EditPosition:
13583         EditPositionDone(TRUE);
13584         break;
13585       case AnalyzeMode:
13586       case AnalyzeFile:
13587         ExitAnalyzeMode();
13588         SendToProgram("force\n", &first);
13589         break;
13590       case TwoMachinesPlay:
13591         GameEnds(EndOfFile, NULL, GE_PLAYER);
13592         ResurrectChessProgram();
13593         SetUserThinkingEnables();
13594         break;
13595       case EndOfGame:
13596         ResurrectChessProgram();
13597         break;
13598       case IcsPlayingBlack:
13599       case IcsPlayingWhite:
13600         DisplayError(_("Warning: You are still playing a game"), 0);
13601         break;
13602       case IcsObserving:
13603         DisplayError(_("Warning: You are still observing a game"), 0);
13604         break;
13605       case IcsExamining:
13606         DisplayError(_("Warning: You are still examining a game"), 0);
13607         break;
13608       case IcsIdle:
13609         break;
13610       case EditGame:
13611       default:
13612         return;
13613     }
13614
13615     pausing = FALSE;
13616     StopClocks();
13617     first.offeredDraw = second.offeredDraw = 0;
13618
13619     if (gameMode == PlayFromGameFile) {
13620         whiteTimeRemaining = timeRemaining[0][currentMove];
13621         blackTimeRemaining = timeRemaining[1][currentMove];
13622         DisplayTitle("");
13623     }
13624
13625     if (gameMode == MachinePlaysWhite ||
13626         gameMode == MachinePlaysBlack ||
13627         gameMode == TwoMachinesPlay ||
13628         gameMode == EndOfGame) {
13629         i = forwardMostMove;
13630         while (i > currentMove) {
13631             SendToProgram("undo\n", &first);
13632             i--;
13633         }
13634         if(!adjustedClock) {
13635         whiteTimeRemaining = timeRemaining[0][currentMove];
13636         blackTimeRemaining = timeRemaining[1][currentMove];
13637         DisplayBothClocks();
13638         }
13639         if (whiteFlag || blackFlag) {
13640             whiteFlag = blackFlag = 0;
13641         }
13642         DisplayTitle("");
13643     }
13644
13645     gameMode = EditGame;
13646     ModeHighlight();
13647     SetGameInfo();
13648 }
13649
13650
13651 void
13652 EditPositionEvent ()
13653 {
13654     if (gameMode == EditPosition) {
13655         EditGameEvent();
13656         return;
13657     }
13658
13659     EditGameEvent();
13660     if (gameMode != EditGame) return;
13661
13662     gameMode = EditPosition;
13663     ModeHighlight();
13664     SetGameInfo();
13665     if (currentMove > 0)
13666       CopyBoard(boards[0], boards[currentMove]);
13667
13668     blackPlaysFirst = !WhiteOnMove(currentMove);
13669     ResetClocks();
13670     currentMove = forwardMostMove = backwardMostMove = 0;
13671     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13672     DisplayMove(-1);
13673 }
13674
13675 void
13676 ExitAnalyzeMode ()
13677 {
13678     /* [DM] icsEngineAnalyze - possible call from other functions */
13679     if (appData.icsEngineAnalyze) {
13680         appData.icsEngineAnalyze = FALSE;
13681
13682         DisplayMessage("",_("Close ICS engine analyze..."));
13683     }
13684     if (first.analysisSupport && first.analyzing) {
13685       SendToProgram("exit\n", &first);
13686       first.analyzing = FALSE;
13687     }
13688     thinkOutput[0] = NULLCHAR;
13689 }
13690
13691 void
13692 EditPositionDone (Boolean fakeRights)
13693 {
13694     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13695
13696     startedFromSetupPosition = TRUE;
13697     InitChessProgram(&first, FALSE);
13698     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13699       boards[0][EP_STATUS] = EP_NONE;
13700       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13701     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13702         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13703         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13704       } else boards[0][CASTLING][2] = NoRights;
13705     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13706         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13707         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13708       } else boards[0][CASTLING][5] = NoRights;
13709     }
13710     SendToProgram("force\n", &first);
13711     if (blackPlaysFirst) {
13712         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13713         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13714         currentMove = forwardMostMove = backwardMostMove = 1;
13715         CopyBoard(boards[1], boards[0]);
13716     } else {
13717         currentMove = forwardMostMove = backwardMostMove = 0;
13718     }
13719     SendBoard(&first, forwardMostMove);
13720     if (appData.debugMode) {
13721         fprintf(debugFP, "EditPosDone\n");
13722     }
13723     DisplayTitle("");
13724     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13725     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13726     gameMode = EditGame;
13727     ModeHighlight();
13728     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13729     ClearHighlights(); /* [AS] */
13730 }
13731
13732 /* Pause for `ms' milliseconds */
13733 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13734 void
13735 TimeDelay (long ms)
13736 {
13737     TimeMark m1, m2;
13738
13739     GetTimeMark(&m1);
13740     do {
13741         GetTimeMark(&m2);
13742     } while (SubtractTimeMarks(&m2, &m1) < ms);
13743 }
13744
13745 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13746 void
13747 SendMultiLineToICS (char *buf)
13748 {
13749     char temp[MSG_SIZ+1], *p;
13750     int len;
13751
13752     len = strlen(buf);
13753     if (len > MSG_SIZ)
13754       len = MSG_SIZ;
13755
13756     strncpy(temp, buf, len);
13757     temp[len] = 0;
13758
13759     p = temp;
13760     while (*p) {
13761         if (*p == '\n' || *p == '\r')
13762           *p = ' ';
13763         ++p;
13764     }
13765
13766     strcat(temp, "\n");
13767     SendToICS(temp);
13768     SendToPlayer(temp, strlen(temp));
13769 }
13770
13771 void
13772 SetWhiteToPlayEvent ()
13773 {
13774     if (gameMode == EditPosition) {
13775         blackPlaysFirst = FALSE;
13776         DisplayBothClocks();    /* works because currentMove is 0 */
13777     } else if (gameMode == IcsExamining) {
13778         SendToICS(ics_prefix);
13779         SendToICS("tomove white\n");
13780     }
13781 }
13782
13783 void
13784 SetBlackToPlayEvent ()
13785 {
13786     if (gameMode == EditPosition) {
13787         blackPlaysFirst = TRUE;
13788         currentMove = 1;        /* kludge */
13789         DisplayBothClocks();
13790         currentMove = 0;
13791     } else if (gameMode == IcsExamining) {
13792         SendToICS(ics_prefix);
13793         SendToICS("tomove black\n");
13794     }
13795 }
13796
13797 void
13798 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13799 {
13800     char buf[MSG_SIZ];
13801     ChessSquare piece = boards[0][y][x];
13802
13803     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13804
13805     switch (selection) {
13806       case ClearBoard:
13807         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13808             SendToICS(ics_prefix);
13809             SendToICS("bsetup clear\n");
13810         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13811             SendToICS(ics_prefix);
13812             SendToICS("clearboard\n");
13813         } else {
13814             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13815                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13816                 for (y = 0; y < BOARD_HEIGHT; y++) {
13817                     if (gameMode == IcsExamining) {
13818                         if (boards[currentMove][y][x] != EmptySquare) {
13819                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13820                                     AAA + x, ONE + y);
13821                             SendToICS(buf);
13822                         }
13823                     } else {
13824                         boards[0][y][x] = p;
13825                     }
13826                 }
13827             }
13828         }
13829         if (gameMode == EditPosition) {
13830             DrawPosition(FALSE, boards[0]);
13831         }
13832         break;
13833
13834       case WhitePlay:
13835         SetWhiteToPlayEvent();
13836         break;
13837
13838       case BlackPlay:
13839         SetBlackToPlayEvent();
13840         break;
13841
13842       case EmptySquare:
13843         if (gameMode == IcsExamining) {
13844             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13845             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13846             SendToICS(buf);
13847         } else {
13848             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13849                 if(x == BOARD_LEFT-2) {
13850                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13851                     boards[0][y][1] = 0;
13852                 } else
13853                 if(x == BOARD_RGHT+1) {
13854                     if(y >= gameInfo.holdingsSize) break;
13855                     boards[0][y][BOARD_WIDTH-2] = 0;
13856                 } else break;
13857             }
13858             boards[0][y][x] = EmptySquare;
13859             DrawPosition(FALSE, boards[0]);
13860         }
13861         break;
13862
13863       case PromotePiece:
13864         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13865            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13866             selection = (ChessSquare) (PROMOTED piece);
13867         } else if(piece == EmptySquare) selection = WhiteSilver;
13868         else selection = (ChessSquare)((int)piece - 1);
13869         goto defaultlabel;
13870
13871       case DemotePiece:
13872         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13873            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13874             selection = (ChessSquare) (DEMOTED piece);
13875         } else if(piece == EmptySquare) selection = BlackSilver;
13876         else selection = (ChessSquare)((int)piece + 1);
13877         goto defaultlabel;
13878
13879       case WhiteQueen:
13880       case BlackQueen:
13881         if(gameInfo.variant == VariantShatranj ||
13882            gameInfo.variant == VariantXiangqi  ||
13883            gameInfo.variant == VariantCourier  ||
13884            gameInfo.variant == VariantMakruk     )
13885             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13886         goto defaultlabel;
13887
13888       case WhiteKing:
13889       case BlackKing:
13890         if(gameInfo.variant == VariantXiangqi)
13891             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13892         if(gameInfo.variant == VariantKnightmate)
13893             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13894       default:
13895         defaultlabel:
13896         if (gameMode == IcsExamining) {
13897             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13898             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13899                      PieceToChar(selection), AAA + x, ONE + y);
13900             SendToICS(buf);
13901         } else {
13902             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13903                 int n;
13904                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13905                     n = PieceToNumber(selection - BlackPawn);
13906                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13907                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13908                     boards[0][BOARD_HEIGHT-1-n][1]++;
13909                 } else
13910                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13911                     n = PieceToNumber(selection);
13912                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13913                     boards[0][n][BOARD_WIDTH-1] = selection;
13914                     boards[0][n][BOARD_WIDTH-2]++;
13915                 }
13916             } else
13917             boards[0][y][x] = selection;
13918             DrawPosition(TRUE, boards[0]);
13919         }
13920         break;
13921     }
13922 }
13923
13924
13925 void
13926 DropMenuEvent (ChessSquare selection, int x, int y)
13927 {
13928     ChessMove moveType;
13929
13930     switch (gameMode) {
13931       case IcsPlayingWhite:
13932       case MachinePlaysBlack:
13933         if (!WhiteOnMove(currentMove)) {
13934             DisplayMoveError(_("It is Black's turn"));
13935             return;
13936         }
13937         moveType = WhiteDrop;
13938         break;
13939       case IcsPlayingBlack:
13940       case MachinePlaysWhite:
13941         if (WhiteOnMove(currentMove)) {
13942             DisplayMoveError(_("It is White's turn"));
13943             return;
13944         }
13945         moveType = BlackDrop;
13946         break;
13947       case EditGame:
13948         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13949         break;
13950       default:
13951         return;
13952     }
13953
13954     if (moveType == BlackDrop && selection < BlackPawn) {
13955       selection = (ChessSquare) ((int) selection
13956                                  + (int) BlackPawn - (int) WhitePawn);
13957     }
13958     if (boards[currentMove][y][x] != EmptySquare) {
13959         DisplayMoveError(_("That square is occupied"));
13960         return;
13961     }
13962
13963     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13964 }
13965
13966 void
13967 AcceptEvent ()
13968 {
13969     /* Accept a pending offer of any kind from opponent */
13970
13971     if (appData.icsActive) {
13972         SendToICS(ics_prefix);
13973         SendToICS("accept\n");
13974     } else if (cmailMsgLoaded) {
13975         if (currentMove == cmailOldMove &&
13976             commentList[cmailOldMove] != NULL &&
13977             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13978                    "Black offers a draw" : "White offers a draw")) {
13979             TruncateGame();
13980             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13981             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13982         } else {
13983             DisplayError(_("There is no pending offer on this move"), 0);
13984             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13985         }
13986     } else {
13987         /* Not used for offers from chess program */
13988     }
13989 }
13990
13991 void
13992 DeclineEvent ()
13993 {
13994     /* Decline a pending offer of any kind from opponent */
13995
13996     if (appData.icsActive) {
13997         SendToICS(ics_prefix);
13998         SendToICS("decline\n");
13999     } else if (cmailMsgLoaded) {
14000         if (currentMove == cmailOldMove &&
14001             commentList[cmailOldMove] != NULL &&
14002             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14003                    "Black offers a draw" : "White offers a draw")) {
14004 #ifdef NOTDEF
14005             AppendComment(cmailOldMove, "Draw declined", TRUE);
14006             DisplayComment(cmailOldMove - 1, "Draw declined");
14007 #endif /*NOTDEF*/
14008         } else {
14009             DisplayError(_("There is no pending offer on this move"), 0);
14010         }
14011     } else {
14012         /* Not used for offers from chess program */
14013     }
14014 }
14015
14016 void
14017 RematchEvent ()
14018 {
14019     /* Issue ICS rematch command */
14020     if (appData.icsActive) {
14021         SendToICS(ics_prefix);
14022         SendToICS("rematch\n");
14023     }
14024 }
14025
14026 void
14027 CallFlagEvent ()
14028 {
14029     /* Call your opponent's flag (claim a win on time) */
14030     if (appData.icsActive) {
14031         SendToICS(ics_prefix);
14032         SendToICS("flag\n");
14033     } else {
14034         switch (gameMode) {
14035           default:
14036             return;
14037           case MachinePlaysWhite:
14038             if (whiteFlag) {
14039                 if (blackFlag)
14040                   GameEnds(GameIsDrawn, "Both players ran out of time",
14041                            GE_PLAYER);
14042                 else
14043                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14044             } else {
14045                 DisplayError(_("Your opponent is not out of time"), 0);
14046             }
14047             break;
14048           case MachinePlaysBlack:
14049             if (blackFlag) {
14050                 if (whiteFlag)
14051                   GameEnds(GameIsDrawn, "Both players ran out of time",
14052                            GE_PLAYER);
14053                 else
14054                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14055             } else {
14056                 DisplayError(_("Your opponent is not out of time"), 0);
14057             }
14058             break;
14059         }
14060     }
14061 }
14062
14063 void
14064 ClockClick (int which)
14065 {       // [HGM] code moved to back-end from winboard.c
14066         if(which) { // black clock
14067           if (gameMode == EditPosition || gameMode == IcsExamining) {
14068             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14069             SetBlackToPlayEvent();
14070           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14071           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14072           } else if (shiftKey) {
14073             AdjustClock(which, -1);
14074           } else if (gameMode == IcsPlayingWhite ||
14075                      gameMode == MachinePlaysBlack) {
14076             CallFlagEvent();
14077           }
14078         } else { // white clock
14079           if (gameMode == EditPosition || gameMode == IcsExamining) {
14080             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14081             SetWhiteToPlayEvent();
14082           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14083           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14084           } else if (shiftKey) {
14085             AdjustClock(which, -1);
14086           } else if (gameMode == IcsPlayingBlack ||
14087                    gameMode == MachinePlaysWhite) {
14088             CallFlagEvent();
14089           }
14090         }
14091 }
14092
14093 void
14094 DrawEvent ()
14095 {
14096     /* Offer draw or accept pending draw offer from opponent */
14097
14098     if (appData.icsActive) {
14099         /* Note: tournament rules require draw offers to be
14100            made after you make your move but before you punch
14101            your clock.  Currently ICS doesn't let you do that;
14102            instead, you immediately punch your clock after making
14103            a move, but you can offer a draw at any time. */
14104
14105         SendToICS(ics_prefix);
14106         SendToICS("draw\n");
14107         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14108     } else if (cmailMsgLoaded) {
14109         if (currentMove == cmailOldMove &&
14110             commentList[cmailOldMove] != NULL &&
14111             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14112                    "Black offers a draw" : "White offers a draw")) {
14113             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14114             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14115         } else if (currentMove == cmailOldMove + 1) {
14116             char *offer = WhiteOnMove(cmailOldMove) ?
14117               "White offers a draw" : "Black offers a draw";
14118             AppendComment(currentMove, offer, TRUE);
14119             DisplayComment(currentMove - 1, offer);
14120             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14121         } else {
14122             DisplayError(_("You must make your move before offering a draw"), 0);
14123             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14124         }
14125     } else if (first.offeredDraw) {
14126         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14127     } else {
14128         if (first.sendDrawOffers) {
14129             SendToProgram("draw\n", &first);
14130             userOfferedDraw = TRUE;
14131         }
14132     }
14133 }
14134
14135 void
14136 AdjournEvent ()
14137 {
14138     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14139
14140     if (appData.icsActive) {
14141         SendToICS(ics_prefix);
14142         SendToICS("adjourn\n");
14143     } else {
14144         /* Currently GNU Chess doesn't offer or accept Adjourns */
14145     }
14146 }
14147
14148
14149 void
14150 AbortEvent ()
14151 {
14152     /* Offer Abort or accept pending Abort offer from opponent */
14153
14154     if (appData.icsActive) {
14155         SendToICS(ics_prefix);
14156         SendToICS("abort\n");
14157     } else {
14158         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14159     }
14160 }
14161
14162 void
14163 ResignEvent ()
14164 {
14165     /* Resign.  You can do this even if it's not your turn. */
14166
14167     if (appData.icsActive) {
14168         SendToICS(ics_prefix);
14169         SendToICS("resign\n");
14170     } else {
14171         switch (gameMode) {
14172           case MachinePlaysWhite:
14173             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14174             break;
14175           case MachinePlaysBlack:
14176             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14177             break;
14178           case EditGame:
14179             if (cmailMsgLoaded) {
14180                 TruncateGame();
14181                 if (WhiteOnMove(cmailOldMove)) {
14182                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14183                 } else {
14184                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14185                 }
14186                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14187             }
14188             break;
14189           default:
14190             break;
14191         }
14192     }
14193 }
14194
14195
14196 void
14197 StopObservingEvent ()
14198 {
14199     /* Stop observing current games */
14200     SendToICS(ics_prefix);
14201     SendToICS("unobserve\n");
14202 }
14203
14204 void
14205 StopExaminingEvent ()
14206 {
14207     /* Stop observing current game */
14208     SendToICS(ics_prefix);
14209     SendToICS("unexamine\n");
14210 }
14211
14212 void
14213 ForwardInner (int target)
14214 {
14215     int limit;
14216
14217     if (appData.debugMode)
14218         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14219                 target, currentMove, forwardMostMove);
14220
14221     if (gameMode == EditPosition)
14222       return;
14223
14224     seekGraphUp = FALSE;
14225     MarkTargetSquares(1);
14226
14227     if (gameMode == PlayFromGameFile && !pausing)
14228       PauseEvent();
14229
14230     if (gameMode == IcsExamining && pausing)
14231       limit = pauseExamForwardMostMove;
14232     else
14233       limit = forwardMostMove;
14234
14235     if (target > limit) target = limit;
14236
14237     if (target > 0 && moveList[target - 1][0]) {
14238         int fromX, fromY, toX, toY;
14239         toX = moveList[target - 1][2] - AAA;
14240         toY = moveList[target - 1][3] - ONE;
14241         if (moveList[target - 1][1] == '@') {
14242             if (appData.highlightLastMove) {
14243                 SetHighlights(-1, -1, toX, toY);
14244             }
14245         } else {
14246             fromX = moveList[target - 1][0] - AAA;
14247             fromY = moveList[target - 1][1] - ONE;
14248             if (target == currentMove + 1) {
14249                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14250             }
14251             if (appData.highlightLastMove) {
14252                 SetHighlights(fromX, fromY, toX, toY);
14253             }
14254         }
14255     }
14256     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14257         gameMode == Training || gameMode == PlayFromGameFile ||
14258         gameMode == AnalyzeFile) {
14259         while (currentMove < target) {
14260             SendMoveToProgram(currentMove++, &first);
14261         }
14262     } else {
14263         currentMove = target;
14264     }
14265
14266     if (gameMode == EditGame || gameMode == EndOfGame) {
14267         whiteTimeRemaining = timeRemaining[0][currentMove];
14268         blackTimeRemaining = timeRemaining[1][currentMove];
14269     }
14270     DisplayBothClocks();
14271     DisplayMove(currentMove - 1);
14272     DrawPosition(FALSE, boards[currentMove]);
14273     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14274     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14275         DisplayComment(currentMove - 1, commentList[currentMove]);
14276     }
14277 }
14278
14279
14280 void
14281 ForwardEvent ()
14282 {
14283     if (gameMode == IcsExamining && !pausing) {
14284         SendToICS(ics_prefix);
14285         SendToICS("forward\n");
14286     } else {
14287         ForwardInner(currentMove + 1);
14288     }
14289 }
14290
14291 void
14292 ToEndEvent ()
14293 {
14294     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14295         /* to optimze, we temporarily turn off analysis mode while we feed
14296          * the remaining moves to the engine. Otherwise we get analysis output
14297          * after each move.
14298          */
14299         if (first.analysisSupport) {
14300           SendToProgram("exit\nforce\n", &first);
14301           first.analyzing = FALSE;
14302         }
14303     }
14304
14305     if (gameMode == IcsExamining && !pausing) {
14306         SendToICS(ics_prefix);
14307         SendToICS("forward 999999\n");
14308     } else {
14309         ForwardInner(forwardMostMove);
14310     }
14311
14312     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14313         /* we have fed all the moves, so reactivate analysis mode */
14314         SendToProgram("analyze\n", &first);
14315         first.analyzing = TRUE;
14316         /*first.maybeThinking = TRUE;*/
14317         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14318     }
14319 }
14320
14321 void
14322 BackwardInner (int target)
14323 {
14324     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14325
14326     if (appData.debugMode)
14327         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14328                 target, currentMove, forwardMostMove);
14329
14330     if (gameMode == EditPosition) return;
14331     seekGraphUp = FALSE;
14332     MarkTargetSquares(1);
14333     if (currentMove <= backwardMostMove) {
14334         ClearHighlights();
14335         DrawPosition(full_redraw, boards[currentMove]);
14336         return;
14337     }
14338     if (gameMode == PlayFromGameFile && !pausing)
14339       PauseEvent();
14340
14341     if (moveList[target][0]) {
14342         int fromX, fromY, toX, toY;
14343         toX = moveList[target][2] - AAA;
14344         toY = moveList[target][3] - ONE;
14345         if (moveList[target][1] == '@') {
14346             if (appData.highlightLastMove) {
14347                 SetHighlights(-1, -1, toX, toY);
14348             }
14349         } else {
14350             fromX = moveList[target][0] - AAA;
14351             fromY = moveList[target][1] - ONE;
14352             if (target == currentMove - 1) {
14353                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14354             }
14355             if (appData.highlightLastMove) {
14356                 SetHighlights(fromX, fromY, toX, toY);
14357             }
14358         }
14359     }
14360     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14361         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14362         while (currentMove > target) {
14363             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14364                 // null move cannot be undone. Reload program with move history before it.
14365                 int i;
14366                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14367                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14368                 }
14369                 SendBoard(&first, i); 
14370                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14371                 break;
14372             }
14373             SendToProgram("undo\n", &first);
14374             currentMove--;
14375         }
14376     } else {
14377         currentMove = target;
14378     }
14379
14380     if (gameMode == EditGame || gameMode == EndOfGame) {
14381         whiteTimeRemaining = timeRemaining[0][currentMove];
14382         blackTimeRemaining = timeRemaining[1][currentMove];
14383     }
14384     DisplayBothClocks();
14385     DisplayMove(currentMove - 1);
14386     DrawPosition(full_redraw, boards[currentMove]);
14387     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14388     // [HGM] PV info: routine tests if comment empty
14389     DisplayComment(currentMove - 1, commentList[currentMove]);
14390 }
14391
14392 void
14393 BackwardEvent ()
14394 {
14395     if (gameMode == IcsExamining && !pausing) {
14396         SendToICS(ics_prefix);
14397         SendToICS("backward\n");
14398     } else {
14399         BackwardInner(currentMove - 1);
14400     }
14401 }
14402
14403 void
14404 ToStartEvent ()
14405 {
14406     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14407         /* to optimize, we temporarily turn off analysis mode while we undo
14408          * all the moves. Otherwise we get analysis output after each undo.
14409          */
14410         if (first.analysisSupport) {
14411           SendToProgram("exit\nforce\n", &first);
14412           first.analyzing = FALSE;
14413         }
14414     }
14415
14416     if (gameMode == IcsExamining && !pausing) {
14417         SendToICS(ics_prefix);
14418         SendToICS("backward 999999\n");
14419     } else {
14420         BackwardInner(backwardMostMove);
14421     }
14422
14423     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14424         /* we have fed all the moves, so reactivate analysis mode */
14425         SendToProgram("analyze\n", &first);
14426         first.analyzing = TRUE;
14427         /*first.maybeThinking = TRUE;*/
14428         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14429     }
14430 }
14431
14432 void
14433 ToNrEvent (int to)
14434 {
14435   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14436   if (to >= forwardMostMove) to = forwardMostMove;
14437   if (to <= backwardMostMove) to = backwardMostMove;
14438   if (to < currentMove) {
14439     BackwardInner(to);
14440   } else {
14441     ForwardInner(to);
14442   }
14443 }
14444
14445 void
14446 RevertEvent (Boolean annotate)
14447 {
14448     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14449         return;
14450     }
14451     if (gameMode != IcsExamining) {
14452         DisplayError(_("You are not examining a game"), 0);
14453         return;
14454     }
14455     if (pausing) {
14456         DisplayError(_("You can't revert while pausing"), 0);
14457         return;
14458     }
14459     SendToICS(ics_prefix);
14460     SendToICS("revert\n");
14461 }
14462
14463 void
14464 RetractMoveEvent ()
14465 {
14466     switch (gameMode) {
14467       case MachinePlaysWhite:
14468       case MachinePlaysBlack:
14469         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14470             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14471             return;
14472         }
14473         if (forwardMostMove < 2) return;
14474         currentMove = forwardMostMove = forwardMostMove - 2;
14475         whiteTimeRemaining = timeRemaining[0][currentMove];
14476         blackTimeRemaining = timeRemaining[1][currentMove];
14477         DisplayBothClocks();
14478         DisplayMove(currentMove - 1);
14479         ClearHighlights();/*!! could figure this out*/
14480         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14481         SendToProgram("remove\n", &first);
14482         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14483         break;
14484
14485       case BeginningOfGame:
14486       default:
14487         break;
14488
14489       case IcsPlayingWhite:
14490       case IcsPlayingBlack:
14491         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14492             SendToICS(ics_prefix);
14493             SendToICS("takeback 2\n");
14494         } else {
14495             SendToICS(ics_prefix);
14496             SendToICS("takeback 1\n");
14497         }
14498         break;
14499     }
14500 }
14501
14502 void
14503 MoveNowEvent ()
14504 {
14505     ChessProgramState *cps;
14506
14507     switch (gameMode) {
14508       case MachinePlaysWhite:
14509         if (!WhiteOnMove(forwardMostMove)) {
14510             DisplayError(_("It is your turn"), 0);
14511             return;
14512         }
14513         cps = &first;
14514         break;
14515       case MachinePlaysBlack:
14516         if (WhiteOnMove(forwardMostMove)) {
14517             DisplayError(_("It is your turn"), 0);
14518             return;
14519         }
14520         cps = &first;
14521         break;
14522       case TwoMachinesPlay:
14523         if (WhiteOnMove(forwardMostMove) ==
14524             (first.twoMachinesColor[0] == 'w')) {
14525             cps = &first;
14526         } else {
14527             cps = &second;
14528         }
14529         break;
14530       case BeginningOfGame:
14531       default:
14532         return;
14533     }
14534     SendToProgram("?\n", cps);
14535 }
14536
14537 void
14538 TruncateGameEvent ()
14539 {
14540     EditGameEvent();
14541     if (gameMode != EditGame) return;
14542     TruncateGame();
14543 }
14544
14545 void
14546 TruncateGame ()
14547 {
14548     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14549     if (forwardMostMove > currentMove) {
14550         if (gameInfo.resultDetails != NULL) {
14551             free(gameInfo.resultDetails);
14552             gameInfo.resultDetails = NULL;
14553             gameInfo.result = GameUnfinished;
14554         }
14555         forwardMostMove = currentMove;
14556         HistorySet(parseList, backwardMostMove, forwardMostMove,
14557                    currentMove-1);
14558     }
14559 }
14560
14561 void
14562 HintEvent ()
14563 {
14564     if (appData.noChessProgram) return;
14565     switch (gameMode) {
14566       case MachinePlaysWhite:
14567         if (WhiteOnMove(forwardMostMove)) {
14568             DisplayError(_("Wait until your turn"), 0);
14569             return;
14570         }
14571         break;
14572       case BeginningOfGame:
14573       case MachinePlaysBlack:
14574         if (!WhiteOnMove(forwardMostMove)) {
14575             DisplayError(_("Wait until your turn"), 0);
14576             return;
14577         }
14578         break;
14579       default:
14580         DisplayError(_("No hint available"), 0);
14581         return;
14582     }
14583     SendToProgram("hint\n", &first);
14584     hintRequested = TRUE;
14585 }
14586
14587 void
14588 BookEvent ()
14589 {
14590     if (appData.noChessProgram) return;
14591     switch (gameMode) {
14592       case MachinePlaysWhite:
14593         if (WhiteOnMove(forwardMostMove)) {
14594             DisplayError(_("Wait until your turn"), 0);
14595             return;
14596         }
14597         break;
14598       case BeginningOfGame:
14599       case MachinePlaysBlack:
14600         if (!WhiteOnMove(forwardMostMove)) {
14601             DisplayError(_("Wait until your turn"), 0);
14602             return;
14603         }
14604         break;
14605       case EditPosition:
14606         EditPositionDone(TRUE);
14607         break;
14608       case TwoMachinesPlay:
14609         return;
14610       default:
14611         break;
14612     }
14613     SendToProgram("bk\n", &first);
14614     bookOutput[0] = NULLCHAR;
14615     bookRequested = TRUE;
14616 }
14617
14618 void
14619 AboutGameEvent ()
14620 {
14621     char *tags = PGNTags(&gameInfo);
14622     TagsPopUp(tags, CmailMsg());
14623     free(tags);
14624 }
14625
14626 /* end button procedures */
14627
14628 void
14629 PrintPosition (FILE *fp, int move)
14630 {
14631     int i, j;
14632
14633     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14634         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14635             char c = PieceToChar(boards[move][i][j]);
14636             fputc(c == 'x' ? '.' : c, fp);
14637             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14638         }
14639     }
14640     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14641       fprintf(fp, "white to play\n");
14642     else
14643       fprintf(fp, "black to play\n");
14644 }
14645
14646 void
14647 PrintOpponents (FILE *fp)
14648 {
14649     if (gameInfo.white != NULL) {
14650         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14651     } else {
14652         fprintf(fp, "\n");
14653     }
14654 }
14655
14656 /* Find last component of program's own name, using some heuristics */
14657 void
14658 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14659 {
14660     char *p, *q, c;
14661     int local = (strcmp(host, "localhost") == 0);
14662     while (!local && (p = strchr(prog, ';')) != NULL) {
14663         p++;
14664         while (*p == ' ') p++;
14665         prog = p;
14666     }
14667     if (*prog == '"' || *prog == '\'') {
14668         q = strchr(prog + 1, *prog);
14669     } else {
14670         q = strchr(prog, ' ');
14671     }
14672     if (q == NULL) q = prog + strlen(prog);
14673     p = q;
14674     while (p >= prog && *p != '/' && *p != '\\') p--;
14675     p++;
14676     if(p == prog && *p == '"') p++;
14677     c = *q; *q = 0;
14678     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14679     memcpy(buf, p, q - p);
14680     buf[q - p] = NULLCHAR;
14681     if (!local) {
14682         strcat(buf, "@");
14683         strcat(buf, host);
14684     }
14685 }
14686
14687 char *
14688 TimeControlTagValue ()
14689 {
14690     char buf[MSG_SIZ];
14691     if (!appData.clockMode) {
14692       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14693     } else if (movesPerSession > 0) {
14694       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14695     } else if (timeIncrement == 0) {
14696       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14697     } else {
14698       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14699     }
14700     return StrSave(buf);
14701 }
14702
14703 void
14704 SetGameInfo ()
14705 {
14706     /* This routine is used only for certain modes */
14707     VariantClass v = gameInfo.variant;
14708     ChessMove r = GameUnfinished;
14709     char *p = NULL;
14710
14711     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14712         r = gameInfo.result;
14713         p = gameInfo.resultDetails;
14714         gameInfo.resultDetails = NULL;
14715     }
14716     ClearGameInfo(&gameInfo);
14717     gameInfo.variant = v;
14718
14719     switch (gameMode) {
14720       case MachinePlaysWhite:
14721         gameInfo.event = StrSave( appData.pgnEventHeader );
14722         gameInfo.site = StrSave(HostName());
14723         gameInfo.date = PGNDate();
14724         gameInfo.round = StrSave("-");
14725         gameInfo.white = StrSave(first.tidy);
14726         gameInfo.black = StrSave(UserName());
14727         gameInfo.timeControl = TimeControlTagValue();
14728         break;
14729
14730       case MachinePlaysBlack:
14731         gameInfo.event = StrSave( appData.pgnEventHeader );
14732         gameInfo.site = StrSave(HostName());
14733         gameInfo.date = PGNDate();
14734         gameInfo.round = StrSave("-");
14735         gameInfo.white = StrSave(UserName());
14736         gameInfo.black = StrSave(first.tidy);
14737         gameInfo.timeControl = TimeControlTagValue();
14738         break;
14739
14740       case TwoMachinesPlay:
14741         gameInfo.event = StrSave( appData.pgnEventHeader );
14742         gameInfo.site = StrSave(HostName());
14743         gameInfo.date = PGNDate();
14744         if (roundNr > 0) {
14745             char buf[MSG_SIZ];
14746             snprintf(buf, MSG_SIZ, "%d", roundNr);
14747             gameInfo.round = StrSave(buf);
14748         } else {
14749             gameInfo.round = StrSave("-");
14750         }
14751         if (first.twoMachinesColor[0] == 'w') {
14752             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14753             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14754         } else {
14755             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14756             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14757         }
14758         gameInfo.timeControl = TimeControlTagValue();
14759         break;
14760
14761       case EditGame:
14762         gameInfo.event = StrSave("Edited game");
14763         gameInfo.site = StrSave(HostName());
14764         gameInfo.date = PGNDate();
14765         gameInfo.round = StrSave("-");
14766         gameInfo.white = StrSave("-");
14767         gameInfo.black = StrSave("-");
14768         gameInfo.result = r;
14769         gameInfo.resultDetails = p;
14770         break;
14771
14772       case EditPosition:
14773         gameInfo.event = StrSave("Edited position");
14774         gameInfo.site = StrSave(HostName());
14775         gameInfo.date = PGNDate();
14776         gameInfo.round = StrSave("-");
14777         gameInfo.white = StrSave("-");
14778         gameInfo.black = StrSave("-");
14779         break;
14780
14781       case IcsPlayingWhite:
14782       case IcsPlayingBlack:
14783       case IcsObserving:
14784       case IcsExamining:
14785         break;
14786
14787       case PlayFromGameFile:
14788         gameInfo.event = StrSave("Game from non-PGN file");
14789         gameInfo.site = StrSave(HostName());
14790         gameInfo.date = PGNDate();
14791         gameInfo.round = StrSave("-");
14792         gameInfo.white = StrSave("?");
14793         gameInfo.black = StrSave("?");
14794         break;
14795
14796       default:
14797         break;
14798     }
14799 }
14800
14801 void
14802 ReplaceComment (int index, char *text)
14803 {
14804     int len;
14805     char *p;
14806     float score;
14807
14808     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14809        pvInfoList[index-1].depth == len &&
14810        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14811        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14812     while (*text == '\n') text++;
14813     len = strlen(text);
14814     while (len > 0 && text[len - 1] == '\n') len--;
14815
14816     if (commentList[index] != NULL)
14817       free(commentList[index]);
14818
14819     if (len == 0) {
14820         commentList[index] = NULL;
14821         return;
14822     }
14823   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14824       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14825       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14826     commentList[index] = (char *) malloc(len + 2);
14827     strncpy(commentList[index], text, len);
14828     commentList[index][len] = '\n';
14829     commentList[index][len + 1] = NULLCHAR;
14830   } else {
14831     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14832     char *p;
14833     commentList[index] = (char *) malloc(len + 7);
14834     safeStrCpy(commentList[index], "{\n", 3);
14835     safeStrCpy(commentList[index]+2, text, len+1);
14836     commentList[index][len+2] = NULLCHAR;
14837     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14838     strcat(commentList[index], "\n}\n");
14839   }
14840 }
14841
14842 void
14843 CrushCRs (char *text)
14844 {
14845   char *p = text;
14846   char *q = text;
14847   char ch;
14848
14849   do {
14850     ch = *p++;
14851     if (ch == '\r') continue;
14852     *q++ = ch;
14853   } while (ch != '\0');
14854 }
14855
14856 void
14857 AppendComment (int index, char *text, Boolean addBraces)
14858 /* addBraces  tells if we should add {} */
14859 {
14860     int oldlen, len;
14861     char *old;
14862
14863 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14864     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14865
14866     CrushCRs(text);
14867     while (*text == '\n') text++;
14868     len = strlen(text);
14869     while (len > 0 && text[len - 1] == '\n') len--;
14870     text[len] = NULLCHAR;
14871
14872     if (len == 0) return;
14873
14874     if (commentList[index] != NULL) {
14875       Boolean addClosingBrace = addBraces;
14876         old = commentList[index];
14877         oldlen = strlen(old);
14878         while(commentList[index][oldlen-1] ==  '\n')
14879           commentList[index][--oldlen] = NULLCHAR;
14880         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14881         safeStrCpy(commentList[index], old, oldlen + len + 6);
14882         free(old);
14883         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14884         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14885           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14886           while (*text == '\n') { text++; len--; }
14887           commentList[index][--oldlen] = NULLCHAR;
14888       }
14889         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14890         else          strcat(commentList[index], "\n");
14891         strcat(commentList[index], text);
14892         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14893         else          strcat(commentList[index], "\n");
14894     } else {
14895         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14896         if(addBraces)
14897           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14898         else commentList[index][0] = NULLCHAR;
14899         strcat(commentList[index], text);
14900         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14901         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14902     }
14903 }
14904
14905 static char *
14906 FindStr (char * text, char * sub_text)
14907 {
14908     char * result = strstr( text, sub_text );
14909
14910     if( result != NULL ) {
14911         result += strlen( sub_text );
14912     }
14913
14914     return result;
14915 }
14916
14917 /* [AS] Try to extract PV info from PGN comment */
14918 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14919 char *
14920 GetInfoFromComment (int index, char * text)
14921 {
14922     char * sep = text, *p;
14923
14924     if( text != NULL && index > 0 ) {
14925         int score = 0;
14926         int depth = 0;
14927         int time = -1, sec = 0, deci;
14928         char * s_eval = FindStr( text, "[%eval " );
14929         char * s_emt = FindStr( text, "[%emt " );
14930
14931         if( s_eval != NULL || s_emt != NULL ) {
14932             /* New style */
14933             char delim;
14934
14935             if( s_eval != NULL ) {
14936                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14937                     return text;
14938                 }
14939
14940                 if( delim != ']' ) {
14941                     return text;
14942                 }
14943             }
14944
14945             if( s_emt != NULL ) {
14946             }
14947                 return text;
14948         }
14949         else {
14950             /* We expect something like: [+|-]nnn.nn/dd */
14951             int score_lo = 0;
14952
14953             if(*text != '{') return text; // [HGM] braces: must be normal comment
14954
14955             sep = strchr( text, '/' );
14956             if( sep == NULL || sep < (text+4) ) {
14957                 return text;
14958             }
14959
14960             p = text;
14961             if(p[1] == '(') { // comment starts with PV
14962                p = strchr(p, ')'); // locate end of PV
14963                if(p == NULL || sep < p+5) return text;
14964                // at this point we have something like "{(.*) +0.23/6 ..."
14965                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14966                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14967                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14968             }
14969             time = -1; sec = -1; deci = -1;
14970             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14971                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14972                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14973                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14974                 return text;
14975             }
14976
14977             if( score_lo < 0 || score_lo >= 100 ) {
14978                 return text;
14979             }
14980
14981             if(sec >= 0) time = 600*time + 10*sec; else
14982             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14983
14984             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14985
14986             /* [HGM] PV time: now locate end of PV info */
14987             while( *++sep >= '0' && *sep <= '9'); // strip depth
14988             if(time >= 0)
14989             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14990             if(sec >= 0)
14991             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14992             if(deci >= 0)
14993             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14994             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
14995         }
14996
14997         if( depth <= 0 ) {
14998             return text;
14999         }
15000
15001         if( time < 0 ) {
15002             time = -1;
15003         }
15004
15005         pvInfoList[index-1].depth = depth;
15006         pvInfoList[index-1].score = score;
15007         pvInfoList[index-1].time  = 10*time; // centi-sec
15008         if(*sep == '}') *sep = 0; else *--sep = '{';
15009         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15010     }
15011     return sep;
15012 }
15013
15014 void
15015 SendToProgram (char *message, ChessProgramState *cps)
15016 {
15017     int count, outCount, error;
15018     char buf[MSG_SIZ];
15019
15020     if (cps->pr == NoProc) return;
15021     Attention(cps);
15022
15023     if (appData.debugMode) {
15024         TimeMark now;
15025         GetTimeMark(&now);
15026         fprintf(debugFP, "%ld >%-6s: %s",
15027                 SubtractTimeMarks(&now, &programStartTime),
15028                 cps->which, message);
15029     }
15030
15031     count = strlen(message);
15032     outCount = OutputToProcess(cps->pr, message, count, &error);
15033     if (outCount < count && !exiting
15034                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15035       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15036       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15037         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15038             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15039                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15040                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15041                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15042             } else {
15043                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15044                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15045                 gameInfo.result = res;
15046             }
15047             gameInfo.resultDetails = StrSave(buf);
15048         }
15049         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15050         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15051     }
15052 }
15053
15054 void
15055 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15056 {
15057     char *end_str;
15058     char buf[MSG_SIZ];
15059     ChessProgramState *cps = (ChessProgramState *)closure;
15060
15061     if (isr != cps->isr) return; /* Killed intentionally */
15062     if (count <= 0) {
15063         if (count == 0) {
15064             RemoveInputSource(cps->isr);
15065             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15066             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15067                     _(cps->which), cps->program);
15068         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15069                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15070                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15071                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15072                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15073                 } else {
15074                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15075                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15076                     gameInfo.result = res;
15077                 }
15078                 gameInfo.resultDetails = StrSave(buf);
15079             }
15080             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15081             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15082         } else {
15083             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15084                     _(cps->which), cps->program);
15085             RemoveInputSource(cps->isr);
15086
15087             /* [AS] Program is misbehaving badly... kill it */
15088             if( count == -2 ) {
15089                 DestroyChildProcess( cps->pr, 9 );
15090                 cps->pr = NoProc;
15091             }
15092
15093             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15094         }
15095         return;
15096     }
15097
15098     if ((end_str = strchr(message, '\r')) != NULL)
15099       *end_str = NULLCHAR;
15100     if ((end_str = strchr(message, '\n')) != NULL)
15101       *end_str = NULLCHAR;
15102
15103     if (appData.debugMode) {
15104         TimeMark now; int print = 1;
15105         char *quote = ""; char c; int i;
15106
15107         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15108                 char start = message[0];
15109                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15110                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15111                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15112                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15113                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15114                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15115                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15116                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15117                    sscanf(message, "hint: %c", &c)!=1 && 
15118                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15119                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15120                     print = (appData.engineComments >= 2);
15121                 }
15122                 message[0] = start; // restore original message
15123         }
15124         if(print) {
15125                 GetTimeMark(&now);
15126                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15127                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15128                         quote,
15129                         message);
15130         }
15131     }
15132
15133     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15134     if (appData.icsEngineAnalyze) {
15135         if (strstr(message, "whisper") != NULL ||
15136              strstr(message, "kibitz") != NULL ||
15137             strstr(message, "tellics") != NULL) return;
15138     }
15139
15140     HandleMachineMove(message, cps);
15141 }
15142
15143
15144 void
15145 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15146 {
15147     char buf[MSG_SIZ];
15148     int seconds;
15149
15150     if( timeControl_2 > 0 ) {
15151         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15152             tc = timeControl_2;
15153         }
15154     }
15155     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15156     inc /= cps->timeOdds;
15157     st  /= cps->timeOdds;
15158
15159     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15160
15161     if (st > 0) {
15162       /* Set exact time per move, normally using st command */
15163       if (cps->stKludge) {
15164         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15165         seconds = st % 60;
15166         if (seconds == 0) {
15167           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15168         } else {
15169           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15170         }
15171       } else {
15172         snprintf(buf, MSG_SIZ, "st %d\n", st);
15173       }
15174     } else {
15175       /* Set conventional or incremental time control, using level command */
15176       if (seconds == 0) {
15177         /* Note old gnuchess bug -- minutes:seconds used to not work.
15178            Fixed in later versions, but still avoid :seconds
15179            when seconds is 0. */
15180         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15181       } else {
15182         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15183                  seconds, inc/1000.);
15184       }
15185     }
15186     SendToProgram(buf, cps);
15187
15188     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15189     /* Orthogonally, limit search to given depth */
15190     if (sd > 0) {
15191       if (cps->sdKludge) {
15192         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15193       } else {
15194         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15195       }
15196       SendToProgram(buf, cps);
15197     }
15198
15199     if(cps->nps >= 0) { /* [HGM] nps */
15200         if(cps->supportsNPS == FALSE)
15201           cps->nps = -1; // don't use if engine explicitly says not supported!
15202         else {
15203           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15204           SendToProgram(buf, cps);
15205         }
15206     }
15207 }
15208
15209 ChessProgramState *
15210 WhitePlayer ()
15211 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15212 {
15213     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15214        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15215         return &second;
15216     return &first;
15217 }
15218
15219 void
15220 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15221 {
15222     char message[MSG_SIZ];
15223     long time, otime;
15224
15225     /* Note: this routine must be called when the clocks are stopped
15226        or when they have *just* been set or switched; otherwise
15227        it will be off by the time since the current tick started.
15228     */
15229     if (machineWhite) {
15230         time = whiteTimeRemaining / 10;
15231         otime = blackTimeRemaining / 10;
15232     } else {
15233         time = blackTimeRemaining / 10;
15234         otime = whiteTimeRemaining / 10;
15235     }
15236     /* [HGM] translate opponent's time by time-odds factor */
15237     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15238     if (appData.debugMode) {
15239         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15240     }
15241
15242     if (time <= 0) time = 1;
15243     if (otime <= 0) otime = 1;
15244
15245     snprintf(message, MSG_SIZ, "time %ld\n", time);
15246     SendToProgram(message, cps);
15247
15248     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15249     SendToProgram(message, cps);
15250 }
15251
15252 int
15253 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15254 {
15255   char buf[MSG_SIZ];
15256   int len = strlen(name);
15257   int val;
15258
15259   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15260     (*p) += len + 1;
15261     sscanf(*p, "%d", &val);
15262     *loc = (val != 0);
15263     while (**p && **p != ' ')
15264       (*p)++;
15265     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15266     SendToProgram(buf, cps);
15267     return TRUE;
15268   }
15269   return FALSE;
15270 }
15271
15272 int
15273 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15274 {
15275   char buf[MSG_SIZ];
15276   int len = strlen(name);
15277   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15278     (*p) += len + 1;
15279     sscanf(*p, "%d", loc);
15280     while (**p && **p != ' ') (*p)++;
15281     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15282     SendToProgram(buf, cps);
15283     return TRUE;
15284   }
15285   return FALSE;
15286 }
15287
15288 int
15289 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15290 {
15291   char buf[MSG_SIZ];
15292   int len = strlen(name);
15293   if (strncmp((*p), name, len) == 0
15294       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15295     (*p) += len + 2;
15296     sscanf(*p, "%[^\"]", loc);
15297     while (**p && **p != '\"') (*p)++;
15298     if (**p == '\"') (*p)++;
15299     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15300     SendToProgram(buf, cps);
15301     return TRUE;
15302   }
15303   return FALSE;
15304 }
15305
15306 int
15307 ParseOption (Option *opt, ChessProgramState *cps)
15308 // [HGM] options: process the string that defines an engine option, and determine
15309 // name, type, default value, and allowed value range
15310 {
15311         char *p, *q, buf[MSG_SIZ];
15312         int n, min = (-1)<<31, max = 1<<31, def;
15313
15314         if(p = strstr(opt->name, " -spin ")) {
15315             if((n = sscanf(p, " -spin %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;
15323         } else if((p = strstr(opt->name, " -slider "))) {
15324             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15325             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15326             if(max < min) max = min; // enforce consistency
15327             if(def < min) def = min;
15328             if(def > max) def = max;
15329             opt->value = def;
15330             opt->min = min;
15331             opt->max = max;
15332             opt->type = Spin; // Slider;
15333         } else if((p = strstr(opt->name, " -string "))) {
15334             opt->textValue = p+9;
15335             opt->type = TextBox;
15336         } else if((p = strstr(opt->name, " -file "))) {
15337             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15338             opt->textValue = p+7;
15339             opt->type = FileName; // FileName;
15340         } else if((p = strstr(opt->name, " -path "))) {
15341             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15342             opt->textValue = p+7;
15343             opt->type = PathName; // PathName;
15344         } else if(p = strstr(opt->name, " -check ")) {
15345             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15346             opt->value = (def != 0);
15347             opt->type = CheckBox;
15348         } else if(p = strstr(opt->name, " -combo ")) {
15349             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15350             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15351             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15352             opt->value = n = 0;
15353             while(q = StrStr(q, " /// ")) {
15354                 n++; *q = 0;    // count choices, and null-terminate each of them
15355                 q += 5;
15356                 if(*q == '*') { // remember default, which is marked with * prefix
15357                     q++;
15358                     opt->value = n;
15359                 }
15360                 cps->comboList[cps->comboCnt++] = q;
15361             }
15362             cps->comboList[cps->comboCnt++] = NULL;
15363             opt->max = n + 1;
15364             opt->type = ComboBox;
15365         } else if(p = strstr(opt->name, " -button")) {
15366             opt->type = Button;
15367         } else if(p = strstr(opt->name, " -save")) {
15368             opt->type = SaveButton;
15369         } else return FALSE;
15370         *p = 0; // terminate option name
15371         // now look if the command-line options define a setting for this engine option.
15372         if(cps->optionSettings && cps->optionSettings[0])
15373             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15374         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15375           snprintf(buf, MSG_SIZ, "option %s", p);
15376                 if(p = strstr(buf, ",")) *p = 0;
15377                 if(q = strchr(buf, '=')) switch(opt->type) {
15378                     case ComboBox:
15379                         for(n=0; n<opt->max; n++)
15380                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15381                         break;
15382                     case TextBox:
15383                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15384                         break;
15385                     case Spin:
15386                     case CheckBox:
15387                         opt->value = atoi(q+1);
15388                     default:
15389                         break;
15390                 }
15391                 strcat(buf, "\n");
15392                 SendToProgram(buf, cps);
15393         }
15394         return TRUE;
15395 }
15396
15397 void
15398 FeatureDone (ChessProgramState *cps, int val)
15399 {
15400   DelayedEventCallback cb = GetDelayedEvent();
15401   if ((cb == InitBackEnd3 && cps == &first) ||
15402       (cb == SettingsMenuIfReady && cps == &second) ||
15403       (cb == LoadEngine) ||
15404       (cb == TwoMachinesEventIfReady)) {
15405     CancelDelayedEvent();
15406     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15407   }
15408   cps->initDone = val;
15409 }
15410
15411 /* Parse feature command from engine */
15412 void
15413 ParseFeatures (char *args, ChessProgramState *cps)
15414 {
15415   char *p = args;
15416   char *q;
15417   int val;
15418   char buf[MSG_SIZ];
15419
15420   for (;;) {
15421     while (*p == ' ') p++;
15422     if (*p == NULLCHAR) return;
15423
15424     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15425     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15426     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15427     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15428     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15429     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15430     if (BoolFeature(&p, "reuse", &val, cps)) {
15431       /* Engine can disable reuse, but can't enable it if user said no */
15432       if (!val) cps->reuse = FALSE;
15433       continue;
15434     }
15435     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15436     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15437       if (gameMode == TwoMachinesPlay) {
15438         DisplayTwoMachinesTitle();
15439       } else {
15440         DisplayTitle("");
15441       }
15442       continue;
15443     }
15444     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15445     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15446     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15447     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15448     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15449     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15450     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15451     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15452     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15453     if (IntFeature(&p, "done", &val, cps)) {
15454       FeatureDone(cps, val);
15455       continue;
15456     }
15457     /* Added by Tord: */
15458     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15459     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15460     /* End of additions by Tord */
15461
15462     /* [HGM] added features: */
15463     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15464     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15465     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15466     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15467     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15468     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15469     if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15470         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15471           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15472             SendToProgram(buf, cps);
15473             continue;
15474         }
15475         if(cps->nrOptions >= MAX_OPTIONS) {
15476             cps->nrOptions--;
15477             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15478             DisplayError(buf, 0);
15479         }
15480         continue;
15481     }
15482     /* End of additions by HGM */
15483
15484     /* unknown feature: complain and skip */
15485     q = p;
15486     while (*q && *q != '=') q++;
15487     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15488     SendToProgram(buf, cps);
15489     p = q;
15490     if (*p == '=') {
15491       p++;
15492       if (*p == '\"') {
15493         p++;
15494         while (*p && *p != '\"') p++;
15495         if (*p == '\"') p++;
15496       } else {
15497         while (*p && *p != ' ') p++;
15498       }
15499     }
15500   }
15501
15502 }
15503
15504 void
15505 PeriodicUpdatesEvent (int newState)
15506 {
15507     if (newState == appData.periodicUpdates)
15508       return;
15509
15510     appData.periodicUpdates=newState;
15511
15512     /* Display type changes, so update it now */
15513 //    DisplayAnalysis();
15514
15515     /* Get the ball rolling again... */
15516     if (newState) {
15517         AnalysisPeriodicEvent(1);
15518         StartAnalysisClock();
15519     }
15520 }
15521
15522 void
15523 PonderNextMoveEvent (int newState)
15524 {
15525     if (newState == appData.ponderNextMove) return;
15526     if (gameMode == EditPosition) EditPositionDone(TRUE);
15527     if (newState) {
15528         SendToProgram("hard\n", &first);
15529         if (gameMode == TwoMachinesPlay) {
15530             SendToProgram("hard\n", &second);
15531         }
15532     } else {
15533         SendToProgram("easy\n", &first);
15534         thinkOutput[0] = NULLCHAR;
15535         if (gameMode == TwoMachinesPlay) {
15536             SendToProgram("easy\n", &second);
15537         }
15538     }
15539     appData.ponderNextMove = newState;
15540 }
15541
15542 void
15543 NewSettingEvent (int option, int *feature, char *command, int value)
15544 {
15545     char buf[MSG_SIZ];
15546
15547     if (gameMode == EditPosition) EditPositionDone(TRUE);
15548     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15549     if(feature == NULL || *feature) SendToProgram(buf, &first);
15550     if (gameMode == TwoMachinesPlay) {
15551         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15552     }
15553 }
15554
15555 void
15556 ShowThinkingEvent ()
15557 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15558 {
15559     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15560     int newState = appData.showThinking
15561         // [HGM] thinking: other features now need thinking output as well
15562         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15563
15564     if (oldState == newState) return;
15565     oldState = newState;
15566     if (gameMode == EditPosition) EditPositionDone(TRUE);
15567     if (oldState) {
15568         SendToProgram("post\n", &first);
15569         if (gameMode == TwoMachinesPlay) {
15570             SendToProgram("post\n", &second);
15571         }
15572     } else {
15573         SendToProgram("nopost\n", &first);
15574         thinkOutput[0] = NULLCHAR;
15575         if (gameMode == TwoMachinesPlay) {
15576             SendToProgram("nopost\n", &second);
15577         }
15578     }
15579 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15580 }
15581
15582 void
15583 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15584 {
15585   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15586   if (pr == NoProc) return;
15587   AskQuestion(title, question, replyPrefix, pr);
15588 }
15589
15590 void
15591 TypeInEvent (char firstChar)
15592 {
15593     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15594         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15595         gameMode == AnalyzeMode || gameMode == EditGame || 
15596         gameMode == EditPosition || gameMode == IcsExamining ||
15597         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15598         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15599                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15600                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15601         gameMode == Training) PopUpMoveDialog(firstChar);
15602 }
15603
15604 void
15605 TypeInDoneEvent (char *move)
15606 {
15607         Board board;
15608         int n, fromX, fromY, toX, toY;
15609         char promoChar;
15610         ChessMove moveType;
15611
15612         // [HGM] FENedit
15613         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15614                 EditPositionPasteFEN(move);
15615                 return;
15616         }
15617         // [HGM] movenum: allow move number to be typed in any mode
15618         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15619           ToNrEvent(2*n-1);
15620           return;
15621         }
15622
15623       if (gameMode != EditGame && currentMove != forwardMostMove && 
15624         gameMode != Training) {
15625         DisplayMoveError(_("Displayed move is not current"));
15626       } else {
15627         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15628           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15629         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15630         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15631           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15632           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15633         } else {
15634           DisplayMoveError(_("Could not parse move"));
15635         }
15636       }
15637 }
15638
15639 void
15640 DisplayMove (int moveNumber)
15641 {
15642     char message[MSG_SIZ];
15643     char res[MSG_SIZ];
15644     char cpThinkOutput[MSG_SIZ];
15645
15646     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15647
15648     if (moveNumber == forwardMostMove - 1 ||
15649         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15650
15651         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15652
15653         if (strchr(cpThinkOutput, '\n')) {
15654             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15655         }
15656     } else {
15657         *cpThinkOutput = NULLCHAR;
15658     }
15659
15660     /* [AS] Hide thinking from human user */
15661     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15662         *cpThinkOutput = NULLCHAR;
15663         if( thinkOutput[0] != NULLCHAR ) {
15664             int i;
15665
15666             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15667                 cpThinkOutput[i] = '.';
15668             }
15669             cpThinkOutput[i] = NULLCHAR;
15670             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15671         }
15672     }
15673
15674     if (moveNumber == forwardMostMove - 1 &&
15675         gameInfo.resultDetails != NULL) {
15676         if (gameInfo.resultDetails[0] == NULLCHAR) {
15677           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15678         } else {
15679           snprintf(res, MSG_SIZ, " {%s} %s",
15680                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15681         }
15682     } else {
15683         res[0] = NULLCHAR;
15684     }
15685
15686     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15687         DisplayMessage(res, cpThinkOutput);
15688     } else {
15689       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15690                 WhiteOnMove(moveNumber) ? " " : ".. ",
15691                 parseList[moveNumber], res);
15692         DisplayMessage(message, cpThinkOutput);
15693     }
15694 }
15695
15696 void
15697 DisplayComment (int moveNumber, char *text)
15698 {
15699     char title[MSG_SIZ];
15700
15701     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15702       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15703     } else {
15704       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15705               WhiteOnMove(moveNumber) ? " " : ".. ",
15706               parseList[moveNumber]);
15707     }
15708     if (text != NULL && (appData.autoDisplayComment || commentUp))
15709         CommentPopUp(title, text);
15710 }
15711
15712 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15713  * might be busy thinking or pondering.  It can be omitted if your
15714  * gnuchess is configured to stop thinking immediately on any user
15715  * input.  However, that gnuchess feature depends on the FIONREAD
15716  * ioctl, which does not work properly on some flavors of Unix.
15717  */
15718 void
15719 Attention (ChessProgramState *cps)
15720 {
15721 #if ATTENTION
15722     if (!cps->useSigint) return;
15723     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15724     switch (gameMode) {
15725       case MachinePlaysWhite:
15726       case MachinePlaysBlack:
15727       case TwoMachinesPlay:
15728       case IcsPlayingWhite:
15729       case IcsPlayingBlack:
15730       case AnalyzeMode:
15731       case AnalyzeFile:
15732         /* Skip if we know it isn't thinking */
15733         if (!cps->maybeThinking) return;
15734         if (appData.debugMode)
15735           fprintf(debugFP, "Interrupting %s\n", cps->which);
15736         InterruptChildProcess(cps->pr);
15737         cps->maybeThinking = FALSE;
15738         break;
15739       default:
15740         break;
15741     }
15742 #endif /*ATTENTION*/
15743 }
15744
15745 int
15746 CheckFlags ()
15747 {
15748     if (whiteTimeRemaining <= 0) {
15749         if (!whiteFlag) {
15750             whiteFlag = TRUE;
15751             if (appData.icsActive) {
15752                 if (appData.autoCallFlag &&
15753                     gameMode == IcsPlayingBlack && !blackFlag) {
15754                   SendToICS(ics_prefix);
15755                   SendToICS("flag\n");
15756                 }
15757             } else {
15758                 if (blackFlag) {
15759                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15760                 } else {
15761                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15762                     if (appData.autoCallFlag) {
15763                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15764                         return TRUE;
15765                     }
15766                 }
15767             }
15768         }
15769     }
15770     if (blackTimeRemaining <= 0) {
15771         if (!blackFlag) {
15772             blackFlag = TRUE;
15773             if (appData.icsActive) {
15774                 if (appData.autoCallFlag &&
15775                     gameMode == IcsPlayingWhite && !whiteFlag) {
15776                   SendToICS(ics_prefix);
15777                   SendToICS("flag\n");
15778                 }
15779             } else {
15780                 if (whiteFlag) {
15781                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15782                 } else {
15783                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15784                     if (appData.autoCallFlag) {
15785                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15786                         return TRUE;
15787                     }
15788                 }
15789             }
15790         }
15791     }
15792     return FALSE;
15793 }
15794
15795 void
15796 CheckTimeControl ()
15797 {
15798     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15799         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15800
15801     /*
15802      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15803      */
15804     if ( !WhiteOnMove(forwardMostMove) ) {
15805         /* White made time control */
15806         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15807         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15808         /* [HGM] time odds: correct new time quota for time odds! */
15809                                             / WhitePlayer()->timeOdds;
15810         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15811     } else {
15812         lastBlack -= blackTimeRemaining;
15813         /* Black made time control */
15814         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15815                                             / WhitePlayer()->other->timeOdds;
15816         lastWhite = whiteTimeRemaining;
15817     }
15818 }
15819
15820 void
15821 DisplayBothClocks ()
15822 {
15823     int wom = gameMode == EditPosition ?
15824       !blackPlaysFirst : WhiteOnMove(currentMove);
15825     DisplayWhiteClock(whiteTimeRemaining, wom);
15826     DisplayBlackClock(blackTimeRemaining, !wom);
15827 }
15828
15829
15830 /* Timekeeping seems to be a portability nightmare.  I think everyone
15831    has ftime(), but I'm really not sure, so I'm including some ifdefs
15832    to use other calls if you don't.  Clocks will be less accurate if
15833    you have neither ftime nor gettimeofday.
15834 */
15835
15836 /* VS 2008 requires the #include outside of the function */
15837 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15838 #include <sys/timeb.h>
15839 #endif
15840
15841 /* Get the current time as a TimeMark */
15842 void
15843 GetTimeMark (TimeMark *tm)
15844 {
15845 #if HAVE_GETTIMEOFDAY
15846
15847     struct timeval timeVal;
15848     struct timezone timeZone;
15849
15850     gettimeofday(&timeVal, &timeZone);
15851     tm->sec = (long) timeVal.tv_sec;
15852     tm->ms = (int) (timeVal.tv_usec / 1000L);
15853
15854 #else /*!HAVE_GETTIMEOFDAY*/
15855 #if HAVE_FTIME
15856
15857 // include <sys/timeb.h> / moved to just above start of function
15858     struct timeb timeB;
15859
15860     ftime(&timeB);
15861     tm->sec = (long) timeB.time;
15862     tm->ms = (int) timeB.millitm;
15863
15864 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15865     tm->sec = (long) time(NULL);
15866     tm->ms = 0;
15867 #endif
15868 #endif
15869 }
15870
15871 /* Return the difference in milliseconds between two
15872    time marks.  We assume the difference will fit in a long!
15873 */
15874 long
15875 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
15876 {
15877     return 1000L*(tm2->sec - tm1->sec) +
15878            (long) (tm2->ms - tm1->ms);
15879 }
15880
15881
15882 /*
15883  * Code to manage the game clocks.
15884  *
15885  * In tournament play, black starts the clock and then white makes a move.
15886  * We give the human user a slight advantage if he is playing white---the
15887  * clocks don't run until he makes his first move, so it takes zero time.
15888  * Also, we don't account for network lag, so we could get out of sync
15889  * with GNU Chess's clock -- but then, referees are always right.
15890  */
15891
15892 static TimeMark tickStartTM;
15893 static long intendedTickLength;
15894
15895 long
15896 NextTickLength (long timeRemaining)
15897 {
15898     long nominalTickLength, nextTickLength;
15899
15900     if (timeRemaining > 0L && timeRemaining <= 10000L)
15901       nominalTickLength = 100L;
15902     else
15903       nominalTickLength = 1000L;
15904     nextTickLength = timeRemaining % nominalTickLength;
15905     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15906
15907     return nextTickLength;
15908 }
15909
15910 /* Adjust clock one minute up or down */
15911 void
15912 AdjustClock (Boolean which, int dir)
15913 {
15914     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15915     if(which) blackTimeRemaining += 60000*dir;
15916     else      whiteTimeRemaining += 60000*dir;
15917     DisplayBothClocks();
15918     adjustedClock = TRUE;
15919 }
15920
15921 /* Stop clocks and reset to a fresh time control */
15922 void
15923 ResetClocks ()
15924 {
15925     (void) StopClockTimer();
15926     if (appData.icsActive) {
15927         whiteTimeRemaining = blackTimeRemaining = 0;
15928     } else if (searchTime) {
15929         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15930         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15931     } else { /* [HGM] correct new time quote for time odds */
15932         whiteTC = blackTC = fullTimeControlString;
15933         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15934         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15935     }
15936     if (whiteFlag || blackFlag) {
15937         DisplayTitle("");
15938         whiteFlag = blackFlag = FALSE;
15939     }
15940     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15941     DisplayBothClocks();
15942     adjustedClock = FALSE;
15943 }
15944
15945 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15946
15947 /* Decrement running clock by amount of time that has passed */
15948 void
15949 DecrementClocks ()
15950 {
15951     long timeRemaining;
15952     long lastTickLength, fudge;
15953     TimeMark now;
15954
15955     if (!appData.clockMode) return;
15956     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15957
15958     GetTimeMark(&now);
15959
15960     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15961
15962     /* Fudge if we woke up a little too soon */
15963     fudge = intendedTickLength - lastTickLength;
15964     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15965
15966     if (WhiteOnMove(forwardMostMove)) {
15967         if(whiteNPS >= 0) lastTickLength = 0;
15968         timeRemaining = whiteTimeRemaining -= lastTickLength;
15969         if(timeRemaining < 0 && !appData.icsActive) {
15970             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15971             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15972                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15973                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15974             }
15975         }
15976         DisplayWhiteClock(whiteTimeRemaining - fudge,
15977                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15978     } else {
15979         if(blackNPS >= 0) lastTickLength = 0;
15980         timeRemaining = blackTimeRemaining -= lastTickLength;
15981         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15982             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15983             if(suddenDeath) {
15984                 blackStartMove = forwardMostMove;
15985                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15986             }
15987         }
15988         DisplayBlackClock(blackTimeRemaining - fudge,
15989                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15990     }
15991     if (CheckFlags()) return;
15992
15993     tickStartTM = now;
15994     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15995     StartClockTimer(intendedTickLength);
15996
15997     /* if the time remaining has fallen below the alarm threshold, sound the
15998      * alarm. if the alarm has sounded and (due to a takeback or time control
15999      * with increment) the time remaining has increased to a level above the
16000      * threshold, reset the alarm so it can sound again.
16001      */
16002
16003     if (appData.icsActive && appData.icsAlarm) {
16004
16005         /* make sure we are dealing with the user's clock */
16006         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16007                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16008            )) return;
16009
16010         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16011             alarmSounded = FALSE;
16012         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16013             PlayAlarmSound();
16014             alarmSounded = TRUE;
16015         }
16016     }
16017 }
16018
16019
16020 /* A player has just moved, so stop the previously running
16021    clock and (if in clock mode) start the other one.
16022    We redisplay both clocks in case we're in ICS mode, because
16023    ICS gives us an update to both clocks after every move.
16024    Note that this routine is called *after* forwardMostMove
16025    is updated, so the last fractional tick must be subtracted
16026    from the color that is *not* on move now.
16027 */
16028 void
16029 SwitchClocks (int newMoveNr)
16030 {
16031     long lastTickLength;
16032     TimeMark now;
16033     int flagged = FALSE;
16034
16035     GetTimeMark(&now);
16036
16037     if (StopClockTimer() && appData.clockMode) {
16038         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16039         if (!WhiteOnMove(forwardMostMove)) {
16040             if(blackNPS >= 0) lastTickLength = 0;
16041             blackTimeRemaining -= lastTickLength;
16042            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16043 //         if(pvInfoList[forwardMostMove].time == -1)
16044                  pvInfoList[forwardMostMove].time =               // use GUI time
16045                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16046         } else {
16047            if(whiteNPS >= 0) lastTickLength = 0;
16048            whiteTimeRemaining -= lastTickLength;
16049            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16050 //         if(pvInfoList[forwardMostMove].time == -1)
16051                  pvInfoList[forwardMostMove].time =
16052                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16053         }
16054         flagged = CheckFlags();
16055     }
16056     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16057     CheckTimeControl();
16058
16059     if (flagged || !appData.clockMode) return;
16060
16061     switch (gameMode) {
16062       case MachinePlaysBlack:
16063       case MachinePlaysWhite:
16064       case BeginningOfGame:
16065         if (pausing) return;
16066         break;
16067
16068       case EditGame:
16069       case PlayFromGameFile:
16070       case IcsExamining:
16071         return;
16072
16073       default:
16074         break;
16075     }
16076
16077     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16078         if(WhiteOnMove(forwardMostMove))
16079              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16080         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16081     }
16082
16083     tickStartTM = now;
16084     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16085       whiteTimeRemaining : blackTimeRemaining);
16086     StartClockTimer(intendedTickLength);
16087 }
16088
16089
16090 /* Stop both clocks */
16091 void
16092 StopClocks ()
16093 {
16094     long lastTickLength;
16095     TimeMark now;
16096
16097     if (!StopClockTimer()) return;
16098     if (!appData.clockMode) return;
16099
16100     GetTimeMark(&now);
16101
16102     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16103     if (WhiteOnMove(forwardMostMove)) {
16104         if(whiteNPS >= 0) lastTickLength = 0;
16105         whiteTimeRemaining -= lastTickLength;
16106         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16107     } else {
16108         if(blackNPS >= 0) lastTickLength = 0;
16109         blackTimeRemaining -= lastTickLength;
16110         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16111     }
16112     CheckFlags();
16113 }
16114
16115 /* Start clock of player on move.  Time may have been reset, so
16116    if clock is already running, stop and restart it. */
16117 void
16118 StartClocks ()
16119 {
16120     (void) StopClockTimer(); /* in case it was running already */
16121     DisplayBothClocks();
16122     if (CheckFlags()) return;
16123
16124     if (!appData.clockMode) return;
16125     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16126
16127     GetTimeMark(&tickStartTM);
16128     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16129       whiteTimeRemaining : blackTimeRemaining);
16130
16131    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16132     whiteNPS = blackNPS = -1;
16133     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16134        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16135         whiteNPS = first.nps;
16136     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16137        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16138         blackNPS = first.nps;
16139     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16140         whiteNPS = second.nps;
16141     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16142         blackNPS = second.nps;
16143     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16144
16145     StartClockTimer(intendedTickLength);
16146 }
16147
16148 char *
16149 TimeString (long ms)
16150 {
16151     long second, minute, hour, day;
16152     char *sign = "";
16153     static char buf[32];
16154
16155     if (ms > 0 && ms <= 9900) {
16156       /* convert milliseconds to tenths, rounding up */
16157       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16158
16159       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16160       return buf;
16161     }
16162
16163     /* convert milliseconds to seconds, rounding up */
16164     /* use floating point to avoid strangeness of integer division
16165        with negative dividends on many machines */
16166     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16167
16168     if (second < 0) {
16169         sign = "-";
16170         second = -second;
16171     }
16172
16173     day = second / (60 * 60 * 24);
16174     second = second % (60 * 60 * 24);
16175     hour = second / (60 * 60);
16176     second = second % (60 * 60);
16177     minute = second / 60;
16178     second = second % 60;
16179
16180     if (day > 0)
16181       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16182               sign, day, hour, minute, second);
16183     else if (hour > 0)
16184       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16185     else
16186       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16187
16188     return buf;
16189 }
16190
16191
16192 /*
16193  * This is necessary because some C libraries aren't ANSI C compliant yet.
16194  */
16195 char *
16196 StrStr (char *string, char *match)
16197 {
16198     int i, length;
16199
16200     length = strlen(match);
16201
16202     for (i = strlen(string) - length; i >= 0; i--, string++)
16203       if (!strncmp(match, string, length))
16204         return string;
16205
16206     return NULL;
16207 }
16208
16209 char *
16210 StrCaseStr (char *string, char *match)
16211 {
16212     int i, j, length;
16213
16214     length = strlen(match);
16215
16216     for (i = strlen(string) - length; i >= 0; i--, string++) {
16217         for (j = 0; j < length; j++) {
16218             if (ToLower(match[j]) != ToLower(string[j]))
16219               break;
16220         }
16221         if (j == length) return string;
16222     }
16223
16224     return NULL;
16225 }
16226
16227 #ifndef _amigados
16228 int
16229 StrCaseCmp (char *s1, char *s2)
16230 {
16231     char c1, c2;
16232
16233     for (;;) {
16234         c1 = ToLower(*s1++);
16235         c2 = ToLower(*s2++);
16236         if (c1 > c2) return 1;
16237         if (c1 < c2) return -1;
16238         if (c1 == NULLCHAR) return 0;
16239     }
16240 }
16241
16242
16243 int
16244 ToLower (int c)
16245 {
16246     return isupper(c) ? tolower(c) : c;
16247 }
16248
16249
16250 int
16251 ToUpper (int c)
16252 {
16253     return islower(c) ? toupper(c) : c;
16254 }
16255 #endif /* !_amigados    */
16256
16257 char *
16258 StrSave (char *s)
16259 {
16260   char *ret;
16261
16262   if ((ret = (char *) malloc(strlen(s) + 1)))
16263     {
16264       safeStrCpy(ret, s, strlen(s)+1);
16265     }
16266   return ret;
16267 }
16268
16269 char *
16270 StrSavePtr (char *s, char **savePtr)
16271 {
16272     if (*savePtr) {
16273         free(*savePtr);
16274     }
16275     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16276       safeStrCpy(*savePtr, s, strlen(s)+1);
16277     }
16278     return(*savePtr);
16279 }
16280
16281 char *
16282 PGNDate ()
16283 {
16284     time_t clock;
16285     struct tm *tm;
16286     char buf[MSG_SIZ];
16287
16288     clock = time((time_t *)NULL);
16289     tm = localtime(&clock);
16290     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16291             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16292     return StrSave(buf);
16293 }
16294
16295
16296 char *
16297 PositionToFEN (int move, char *overrideCastling)
16298 {
16299     int i, j, fromX, fromY, toX, toY;
16300     int whiteToPlay;
16301     char buf[MSG_SIZ];
16302     char *p, *q;
16303     int emptycount;
16304     ChessSquare piece;
16305
16306     whiteToPlay = (gameMode == EditPosition) ?
16307       !blackPlaysFirst : (move % 2 == 0);
16308     p = buf;
16309
16310     /* Piece placement data */
16311     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16312         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16313         emptycount = 0;
16314         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16315             if (boards[move][i][j] == EmptySquare) {
16316                 emptycount++;
16317             } else { ChessSquare piece = boards[move][i][j];
16318                 if (emptycount > 0) {
16319                     if(emptycount<10) /* [HGM] can be >= 10 */
16320                         *p++ = '0' + emptycount;
16321                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16322                     emptycount = 0;
16323                 }
16324                 if(PieceToChar(piece) == '+') {
16325                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16326                     *p++ = '+';
16327                     piece = (ChessSquare)(DEMOTED piece);
16328                 }
16329                 *p++ = PieceToChar(piece);
16330                 if(p[-1] == '~') {
16331                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16332                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16333                     *p++ = '~';
16334                 }
16335             }
16336         }
16337         if (emptycount > 0) {
16338             if(emptycount<10) /* [HGM] can be >= 10 */
16339                 *p++ = '0' + emptycount;
16340             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16341             emptycount = 0;
16342         }
16343         *p++ = '/';
16344     }
16345     *(p - 1) = ' ';
16346
16347     /* [HGM] print Crazyhouse or Shogi holdings */
16348     if( gameInfo.holdingsWidth ) {
16349         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16350         q = p;
16351         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16352             piece = boards[move][i][BOARD_WIDTH-1];
16353             if( piece != EmptySquare )
16354               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16355                   *p++ = PieceToChar(piece);
16356         }
16357         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16358             piece = boards[move][BOARD_HEIGHT-i-1][0];
16359             if( piece != EmptySquare )
16360               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16361                   *p++ = PieceToChar(piece);
16362         }
16363
16364         if( q == p ) *p++ = '-';
16365         *p++ = ']';
16366         *p++ = ' ';
16367     }
16368
16369     /* Active color */
16370     *p++ = whiteToPlay ? 'w' : 'b';
16371     *p++ = ' ';
16372
16373   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16374     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16375   } else {
16376   if(nrCastlingRights) {
16377      q = p;
16378      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16379        /* [HGM] write directly from rights */
16380            if(boards[move][CASTLING][2] != NoRights &&
16381               boards[move][CASTLING][0] != NoRights   )
16382                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16383            if(boards[move][CASTLING][2] != NoRights &&
16384               boards[move][CASTLING][1] != NoRights   )
16385                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16386            if(boards[move][CASTLING][5] != NoRights &&
16387               boards[move][CASTLING][3] != NoRights   )
16388                 *p++ = boards[move][CASTLING][3] + AAA;
16389            if(boards[move][CASTLING][5] != NoRights &&
16390               boards[move][CASTLING][4] != NoRights   )
16391                 *p++ = boards[move][CASTLING][4] + AAA;
16392      } else {
16393
16394         /* [HGM] write true castling rights */
16395         if( nrCastlingRights == 6 ) {
16396             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16397                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16398             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16399                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16400             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16401                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16402             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16403                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16404         }
16405      }
16406      if (q == p) *p++ = '-'; /* No castling rights */
16407      *p++ = ' ';
16408   }
16409
16410   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16411      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16412     /* En passant target square */
16413     if (move > backwardMostMove) {
16414         fromX = moveList[move - 1][0] - AAA;
16415         fromY = moveList[move - 1][1] - ONE;
16416         toX = moveList[move - 1][2] - AAA;
16417         toY = moveList[move - 1][3] - ONE;
16418         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16419             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16420             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16421             fromX == toX) {
16422             /* 2-square pawn move just happened */
16423             *p++ = toX + AAA;
16424             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16425         } else {
16426             *p++ = '-';
16427         }
16428     } else if(move == backwardMostMove) {
16429         // [HGM] perhaps we should always do it like this, and forget the above?
16430         if((signed char)boards[move][EP_STATUS] >= 0) {
16431             *p++ = boards[move][EP_STATUS] + AAA;
16432             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16433         } else {
16434             *p++ = '-';
16435         }
16436     } else {
16437         *p++ = '-';
16438     }
16439     *p++ = ' ';
16440   }
16441   }
16442
16443     /* [HGM] find reversible plies */
16444     {   int i = 0, j=move;
16445
16446         if (appData.debugMode) { int k;
16447             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16448             for(k=backwardMostMove; k<=forwardMostMove; k++)
16449                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16450
16451         }
16452
16453         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16454         if( j == backwardMostMove ) i += initialRulePlies;
16455         sprintf(p, "%d ", i);
16456         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16457     }
16458     /* Fullmove number */
16459     sprintf(p, "%d", (move / 2) + 1);
16460
16461     return StrSave(buf);
16462 }
16463
16464 Boolean
16465 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16466 {
16467     int i, j;
16468     char *p, c;
16469     int emptycount;
16470     ChessSquare piece;
16471
16472     p = fen;
16473
16474     /* [HGM] by default clear Crazyhouse holdings, if present */
16475     if(gameInfo.holdingsWidth) {
16476        for(i=0; i<BOARD_HEIGHT; i++) {
16477            board[i][0]             = EmptySquare; /* black holdings */
16478            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16479            board[i][1]             = (ChessSquare) 0; /* black counts */
16480            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16481        }
16482     }
16483
16484     /* Piece placement data */
16485     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16486         j = 0;
16487         for (;;) {
16488             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16489                 if (*p == '/') p++;
16490                 emptycount = gameInfo.boardWidth - j;
16491                 while (emptycount--)
16492                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16493                 break;
16494 #if(BOARD_FILES >= 10)
16495             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16496                 p++; emptycount=10;
16497                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16498                 while (emptycount--)
16499                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16500 #endif
16501             } else if (isdigit(*p)) {
16502                 emptycount = *p++ - '0';
16503                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16504                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16505                 while (emptycount--)
16506                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16507             } else if (*p == '+' || isalpha(*p)) {
16508                 if (j >= gameInfo.boardWidth) return FALSE;
16509                 if(*p=='+') {
16510                     piece = CharToPiece(*++p);
16511                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16512                     piece = (ChessSquare) (PROMOTED piece ); p++;
16513                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16514                 } else piece = CharToPiece(*p++);
16515
16516                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16517                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16518                     piece = (ChessSquare) (PROMOTED piece);
16519                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16520                     p++;
16521                 }
16522                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16523             } else {
16524                 return FALSE;
16525             }
16526         }
16527     }
16528     while (*p == '/' || *p == ' ') p++;
16529
16530     /* [HGM] look for Crazyhouse holdings here */
16531     while(*p==' ') p++;
16532     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16533         if(*p == '[') p++;
16534         if(*p == '-' ) p++; /* empty holdings */ else {
16535             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16536             /* if we would allow FEN reading to set board size, we would   */
16537             /* have to add holdings and shift the board read so far here   */
16538             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16539                 p++;
16540                 if((int) piece >= (int) BlackPawn ) {
16541                     i = (int)piece - (int)BlackPawn;
16542                     i = PieceToNumber((ChessSquare)i);
16543                     if( i >= gameInfo.holdingsSize ) return FALSE;
16544                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16545                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16546                 } else {
16547                     i = (int)piece - (int)WhitePawn;
16548                     i = PieceToNumber((ChessSquare)i);
16549                     if( i >= gameInfo.holdingsSize ) return FALSE;
16550                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16551                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16552                 }
16553             }
16554         }
16555         if(*p == ']') p++;
16556     }
16557
16558     while(*p == ' ') p++;
16559
16560     /* Active color */
16561     c = *p++;
16562     if(appData.colorNickNames) {
16563       if( c == appData.colorNickNames[0] ) c = 'w'; else
16564       if( c == appData.colorNickNames[1] ) c = 'b';
16565     }
16566     switch (c) {
16567       case 'w':
16568         *blackPlaysFirst = FALSE;
16569         break;
16570       case 'b':
16571         *blackPlaysFirst = TRUE;
16572         break;
16573       default:
16574         return FALSE;
16575     }
16576
16577     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16578     /* return the extra info in global variiables             */
16579
16580     /* set defaults in case FEN is incomplete */
16581     board[EP_STATUS] = EP_UNKNOWN;
16582     for(i=0; i<nrCastlingRights; i++ ) {
16583         board[CASTLING][i] =
16584             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16585     }   /* assume possible unless obviously impossible */
16586     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16587     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16588     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16589                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16590     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16591     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16592     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16593                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16594     FENrulePlies = 0;
16595
16596     while(*p==' ') p++;
16597     if(nrCastlingRights) {
16598       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16599           /* castling indicator present, so default becomes no castlings */
16600           for(i=0; i<nrCastlingRights; i++ ) {
16601                  board[CASTLING][i] = NoRights;
16602           }
16603       }
16604       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16605              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16606              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16607              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16608         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16609
16610         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16611             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16612             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16613         }
16614         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16615             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16616         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16617                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16618         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16619                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16620         switch(c) {
16621           case'K':
16622               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16623               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16624               board[CASTLING][2] = whiteKingFile;
16625               break;
16626           case'Q':
16627               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16628               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16629               board[CASTLING][2] = whiteKingFile;
16630               break;
16631           case'k':
16632               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16633               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16634               board[CASTLING][5] = blackKingFile;
16635               break;
16636           case'q':
16637               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16638               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16639               board[CASTLING][5] = blackKingFile;
16640           case '-':
16641               break;
16642           default: /* FRC castlings */
16643               if(c >= 'a') { /* black rights */
16644                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16645                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16646                   if(i == BOARD_RGHT) break;
16647                   board[CASTLING][5] = i;
16648                   c -= AAA;
16649                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16650                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16651                   if(c > i)
16652                       board[CASTLING][3] = c;
16653                   else
16654                       board[CASTLING][4] = c;
16655               } else { /* white rights */
16656                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16657                     if(board[0][i] == WhiteKing) break;
16658                   if(i == BOARD_RGHT) break;
16659                   board[CASTLING][2] = i;
16660                   c -= AAA - 'a' + 'A';
16661                   if(board[0][c] >= WhiteKing) break;
16662                   if(c > i)
16663                       board[CASTLING][0] = c;
16664                   else
16665                       board[CASTLING][1] = c;
16666               }
16667         }
16668       }
16669       for(i=0; i<nrCastlingRights; i++)
16670         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16671     if (appData.debugMode) {
16672         fprintf(debugFP, "FEN castling rights:");
16673         for(i=0; i<nrCastlingRights; i++)
16674         fprintf(debugFP, " %d", board[CASTLING][i]);
16675         fprintf(debugFP, "\n");
16676     }
16677
16678       while(*p==' ') p++;
16679     }
16680
16681     /* read e.p. field in games that know e.p. capture */
16682     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16683        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16684       if(*p=='-') {
16685         p++; board[EP_STATUS] = EP_NONE;
16686       } else {
16687          char c = *p++ - AAA;
16688
16689          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16690          if(*p >= '0' && *p <='9') p++;
16691          board[EP_STATUS] = c;
16692       }
16693     }
16694
16695
16696     if(sscanf(p, "%d", &i) == 1) {
16697         FENrulePlies = i; /* 50-move ply counter */
16698         /* (The move number is still ignored)    */
16699     }
16700
16701     return TRUE;
16702 }
16703
16704 void
16705 EditPositionPasteFEN (char *fen)
16706 {
16707   if (fen != NULL) {
16708     Board initial_position;
16709
16710     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16711       DisplayError(_("Bad FEN position in clipboard"), 0);
16712       return ;
16713     } else {
16714       int savedBlackPlaysFirst = blackPlaysFirst;
16715       EditPositionEvent();
16716       blackPlaysFirst = savedBlackPlaysFirst;
16717       CopyBoard(boards[0], initial_position);
16718       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16719       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16720       DisplayBothClocks();
16721       DrawPosition(FALSE, boards[currentMove]);
16722     }
16723   }
16724 }
16725
16726 static char cseq[12] = "\\   ";
16727
16728 Boolean
16729 set_cont_sequence (char *new_seq)
16730 {
16731     int len;
16732     Boolean ret;
16733
16734     // handle bad attempts to set the sequence
16735         if (!new_seq)
16736                 return 0; // acceptable error - no debug
16737
16738     len = strlen(new_seq);
16739     ret = (len > 0) && (len < sizeof(cseq));
16740     if (ret)
16741       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16742     else if (appData.debugMode)
16743       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16744     return ret;
16745 }
16746
16747 /*
16748     reformat a source message so words don't cross the width boundary.  internal
16749     newlines are not removed.  returns the wrapped size (no null character unless
16750     included in source message).  If dest is NULL, only calculate the size required
16751     for the dest buffer.  lp argument indicats line position upon entry, and it's
16752     passed back upon exit.
16753 */
16754 int
16755 wrap (char *dest, char *src, int count, int width, int *lp)
16756 {
16757     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16758
16759     cseq_len = strlen(cseq);
16760     old_line = line = *lp;
16761     ansi = len = clen = 0;
16762
16763     for (i=0; i < count; i++)
16764     {
16765         if (src[i] == '\033')
16766             ansi = 1;
16767
16768         // if we hit the width, back up
16769         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16770         {
16771             // store i & len in case the word is too long
16772             old_i = i, old_len = len;
16773
16774             // find the end of the last word
16775             while (i && src[i] != ' ' && src[i] != '\n')
16776             {
16777                 i--;
16778                 len--;
16779             }
16780
16781             // word too long?  restore i & len before splitting it
16782             if ((old_i-i+clen) >= width)
16783             {
16784                 i = old_i;
16785                 len = old_len;
16786             }
16787
16788             // extra space?
16789             if (i && src[i-1] == ' ')
16790                 len--;
16791
16792             if (src[i] != ' ' && src[i] != '\n')
16793             {
16794                 i--;
16795                 if (len)
16796                     len--;
16797             }
16798
16799             // now append the newline and continuation sequence
16800             if (dest)
16801                 dest[len] = '\n';
16802             len++;
16803             if (dest)
16804                 strncpy(dest+len, cseq, cseq_len);
16805             len += cseq_len;
16806             line = cseq_len;
16807             clen = cseq_len;
16808             continue;
16809         }
16810
16811         if (dest)
16812             dest[len] = src[i];
16813         len++;
16814         if (!ansi)
16815             line++;
16816         if (src[i] == '\n')
16817             line = 0;
16818         if (src[i] == 'm')
16819             ansi = 0;
16820     }
16821     if (dest && appData.debugMode)
16822     {
16823         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16824             count, width, line, len, *lp);
16825         show_bytes(debugFP, src, count);
16826         fprintf(debugFP, "\ndest: ");
16827         show_bytes(debugFP, dest, len);
16828         fprintf(debugFP, "\n");
16829     }
16830     *lp = dest ? line : old_line;
16831
16832     return len;
16833 }
16834
16835 // [HGM] vari: routines for shelving variations
16836 Boolean modeRestore = FALSE;
16837
16838 void
16839 PushInner (int firstMove, int lastMove)
16840 {
16841         int i, j, nrMoves = lastMove - firstMove;
16842
16843         // push current tail of game on stack
16844         savedResult[storedGames] = gameInfo.result;
16845         savedDetails[storedGames] = gameInfo.resultDetails;
16846         gameInfo.resultDetails = NULL;
16847         savedFirst[storedGames] = firstMove;
16848         savedLast [storedGames] = lastMove;
16849         savedFramePtr[storedGames] = framePtr;
16850         framePtr -= nrMoves; // reserve space for the boards
16851         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16852             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16853             for(j=0; j<MOVE_LEN; j++)
16854                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16855             for(j=0; j<2*MOVE_LEN; j++)
16856                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16857             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16858             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16859             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16860             pvInfoList[firstMove+i-1].depth = 0;
16861             commentList[framePtr+i] = commentList[firstMove+i];
16862             commentList[firstMove+i] = NULL;
16863         }
16864
16865         storedGames++;
16866         forwardMostMove = firstMove; // truncate game so we can start variation
16867 }
16868
16869 void
16870 PushTail (int firstMove, int lastMove)
16871 {
16872         if(appData.icsActive) { // only in local mode
16873                 forwardMostMove = currentMove; // mimic old ICS behavior
16874                 return;
16875         }
16876         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16877
16878         PushInner(firstMove, lastMove);
16879         if(storedGames == 1) GreyRevert(FALSE);
16880         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16881 }
16882
16883 void
16884 PopInner (Boolean annotate)
16885 {
16886         int i, j, nrMoves;
16887         char buf[8000], moveBuf[20];
16888
16889         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16890         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16891         nrMoves = savedLast[storedGames] - currentMove;
16892         if(annotate) {
16893                 int cnt = 10;
16894                 if(!WhiteOnMove(currentMove))
16895                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16896                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16897                 for(i=currentMove; i<forwardMostMove; i++) {
16898                         if(WhiteOnMove(i))
16899                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16900                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16901                         strcat(buf, moveBuf);
16902                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16903                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16904                 }
16905                 strcat(buf, ")");
16906         }
16907         for(i=1; i<=nrMoves; i++) { // copy last variation back
16908             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16909             for(j=0; j<MOVE_LEN; j++)
16910                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16911             for(j=0; j<2*MOVE_LEN; j++)
16912                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16913             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16914             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16915             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16916             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16917             commentList[currentMove+i] = commentList[framePtr+i];
16918             commentList[framePtr+i] = NULL;
16919         }
16920         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16921         framePtr = savedFramePtr[storedGames];
16922         gameInfo.result = savedResult[storedGames];
16923         if(gameInfo.resultDetails != NULL) {
16924             free(gameInfo.resultDetails);
16925       }
16926         gameInfo.resultDetails = savedDetails[storedGames];
16927         forwardMostMove = currentMove + nrMoves;
16928 }
16929
16930 Boolean
16931 PopTail (Boolean annotate)
16932 {
16933         if(appData.icsActive) return FALSE; // only in local mode
16934         if(!storedGames) return FALSE; // sanity
16935         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16936
16937         PopInner(annotate);
16938         if(currentMove < forwardMostMove) ForwardEvent(); else
16939         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16940
16941         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16942         return TRUE;
16943 }
16944
16945 void
16946 CleanupTail ()
16947 {       // remove all shelved variations
16948         int i;
16949         for(i=0; i<storedGames; i++) {
16950             if(savedDetails[i])
16951                 free(savedDetails[i]);
16952             savedDetails[i] = NULL;
16953         }
16954         for(i=framePtr; i<MAX_MOVES; i++) {
16955                 if(commentList[i]) free(commentList[i]);
16956                 commentList[i] = NULL;
16957         }
16958         framePtr = MAX_MOVES-1;
16959         storedGames = 0;
16960 }
16961
16962 void
16963 LoadVariation (int index, char *text)
16964 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16965         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16966         int level = 0, move;
16967
16968         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16969         // first find outermost bracketing variation
16970         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16971             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16972                 if(*p == '{') wait = '}'; else
16973                 if(*p == '[') wait = ']'; else
16974                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16975                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16976             }
16977             if(*p == wait) wait = NULLCHAR; // closing ]} found
16978             p++;
16979         }
16980         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16981         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16982         end[1] = NULLCHAR; // clip off comment beyond variation
16983         ToNrEvent(currentMove-1);
16984         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16985         // kludge: use ParsePV() to append variation to game
16986         move = currentMove;
16987         ParsePV(start, TRUE, TRUE);
16988         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16989         ClearPremoveHighlights();
16990         CommentPopDown();
16991         ToNrEvent(currentMove+1);
16992 }
16993