12a82a53a89bcb3160c8cc1362a4e961b3b9738c
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "gettext.h"
133
134 #ifdef ENABLE_NLS
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
138 #else
139 # ifdef WIN32
140 #   define _(s) T_(s)
141 #   define N_(s) s
142 # else
143 #   define _(s) (s)
144 #   define N_(s) s
145 #   define T_(s) s
146 # endif
147 #endif
148
149
150 int establish P((void));
151 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
152                          char *buf, int count, int error));
153 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
154                       char *buf, int count, int error));
155 void ics_printf P((char *format, ...));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225
226 #ifdef WIN32
227        extern void ConsoleCreate();
228 #endif
229
230 ChessProgramState *WhitePlayer();
231 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
232 int VerifyDisplayMode P(());
233
234 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
235 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
236 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
237 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
238 void ics_update_width P((int new_width));
239 extern char installDir[MSG_SIZ];
240 VariantClass startVariant; /* [HGM] nicks: initial variant */
241 Boolean abortMatch;
242
243 extern int tinyLayout, smallLayout;
244 ChessProgramStats programStats;
245 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
246 int endPV = -1;
247 static int exiting = 0; /* [HGM] moved to top */
248 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
249 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
250 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
251 int partnerHighlight[2];
252 Boolean partnerBoardValid = 0;
253 char partnerStatus[MSG_SIZ];
254 Boolean partnerUp;
255 Boolean originalFlip;
256 Boolean twoBoards = 0;
257 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
258 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
259 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
260 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
261 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
262 int opponentKibitzes;
263 int lastSavedGame; /* [HGM] save: ID of game */
264 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
265 extern int chatCount;
266 int chattingPartner;
267 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
268 char lastMsg[MSG_SIZ];
269 ChessSquare pieceSweep = EmptySquare;
270 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
271 int promoDefaultAltered;
272
273 /* States for ics_getting_history */
274 #define H_FALSE 0
275 #define H_REQUESTED 1
276 #define H_GOT_REQ_HEADER 2
277 #define H_GOT_UNREQ_HEADER 3
278 #define H_GETTING_MOVES 4
279 #define H_GOT_UNWANTED_HEADER 5
280
281 /* whosays values for GameEnds */
282 #define GE_ICS 0
283 #define GE_ENGINE 1
284 #define GE_PLAYER 2
285 #define GE_FILE 3
286 #define GE_XBOARD 4
287 #define GE_ENGINE1 5
288 #define GE_ENGINE2 6
289
290 /* Maximum number of games in a cmail message */
291 #define CMAIL_MAX_GAMES 20
292
293 /* Different types of move when calling RegisterMove */
294 #define CMAIL_MOVE   0
295 #define CMAIL_RESIGN 1
296 #define CMAIL_DRAW   2
297 #define CMAIL_ACCEPT 3
298
299 /* Different types of result to remember for each game */
300 #define CMAIL_NOT_RESULT 0
301 #define CMAIL_OLD_RESULT 1
302 #define CMAIL_NEW_RESULT 2
303
304 /* Telnet protocol constants */
305 #define TN_WILL 0373
306 #define TN_WONT 0374
307 #define TN_DO   0375
308 #define TN_DONT 0376
309 #define TN_IAC  0377
310 #define TN_ECHO 0001
311 #define TN_SGA  0003
312 #define TN_PORT 23
313
314 char*
315 safeStrCpy (char *dst, const char *src, size_t count)
316 { // [HGM] made safe
317   int i;
318   assert( dst != NULL );
319   assert( src != NULL );
320   assert( count > 0 );
321
322   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
323   if(  i == count && dst[count-1] != NULLCHAR)
324     {
325       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
326       if(appData.debugMode)
327       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
328     }
329
330   return dst;
331 }
332
333 /* Some compiler can't cast u64 to double
334  * This function do the job for us:
335
336  * We use the highest bit for cast, this only
337  * works if the highest bit is not
338  * in use (This should not happen)
339  *
340  * We used this for all compiler
341  */
342 double
343 u64ToDouble (u64 value)
344 {
345   double r;
346   u64 tmp = value & u64Const(0x7fffffffffffffff);
347   r = (double)(s64)tmp;
348   if (value & u64Const(0x8000000000000000))
349        r +=  9.2233720368547758080e18; /* 2^63 */
350  return r;
351 }
352
353 /* Fake up flags for now, as we aren't keeping track of castling
354    availability yet. [HGM] Change of logic: the flag now only
355    indicates the type of castlings allowed by the rule of the game.
356    The actual rights themselves are maintained in the array
357    castlingRights, as part of the game history, and are not probed
358    by this function.
359  */
360 int
361 PosFlags (index)
362 {
363   int flags = F_ALL_CASTLE_OK;
364   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
365   switch (gameInfo.variant) {
366   case VariantSuicide:
367     flags &= ~F_ALL_CASTLE_OK;
368   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
369     flags |= F_IGNORE_CHECK;
370   case VariantLosers:
371     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
372     break;
373   case VariantAtomic:
374     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
375     break;
376   case VariantKriegspiel:
377     flags |= F_KRIEGSPIEL_CAPTURE;
378     break;
379   case VariantCapaRandom:
380   case VariantFischeRandom:
381     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
382   case VariantNoCastle:
383   case VariantShatranj:
384   case VariantCourier:
385   case VariantMakruk:
386   case VariantGrand:
387     flags &= ~F_ALL_CASTLE_OK;
388     break;
389   default:
390     break;
391   }
392   return flags;
393 }
394
395 FILE *gameFileFP, *debugFP;
396 char *currentDebugFile; // [HGM] debug split: to remember name
397
398 /*
399     [AS] Note: sometimes, the sscanf() function is used to parse the input
400     into a fixed-size buffer. Because of this, we must be prepared to
401     receive strings as long as the size of the input buffer, which is currently
402     set to 4K for Windows and 8K for the rest.
403     So, we must either allocate sufficiently large buffers here, or
404     reduce the size of the input buffer in the input reading part.
405 */
406
407 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
408 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
409 char thinkOutput1[MSG_SIZ*10];
410
411 ChessProgramState first, second, pairing;
412
413 /* premove variables */
414 int premoveToX = 0;
415 int premoveToY = 0;
416 int premoveFromX = 0;
417 int premoveFromY = 0;
418 int premovePromoChar = 0;
419 int gotPremove = 0;
420 Boolean alarmSounded;
421 /* end premove variables */
422
423 char *ics_prefix = "$";
424 int ics_type = ICS_GENERIC;
425
426 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
427 int pauseExamForwardMostMove = 0;
428 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
429 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
430 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
431 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
432 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
433 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
434 int whiteFlag = FALSE, blackFlag = FALSE;
435 int userOfferedDraw = FALSE;
436 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
437 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
438 int cmailMoveType[CMAIL_MAX_GAMES];
439 long ics_clock_paused = 0;
440 ProcRef icsPR = NoProc, cmailPR = NoProc;
441 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
442 GameMode gameMode = BeginningOfGame;
443 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
444 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
445 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
446 int hiddenThinkOutputState = 0; /* [AS] */
447 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
448 int adjudicateLossPlies = 6;
449 char white_holding[64], black_holding[64];
450 TimeMark lastNodeCountTime;
451 long lastNodeCount=0;
452 int shiftKey; // [HGM] set by mouse handler
453
454 int have_sent_ICS_logon = 0;
455 int movesPerSession;
456 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
457 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
458 Boolean adjustedClock;
459 long timeControl_2; /* [AS] Allow separate time controls */
460 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
461 long timeRemaining[2][MAX_MOVES];
462 int matchGame = 0, nextGame = 0, roundNr = 0;
463 Boolean waitingForGame = FALSE;
464 TimeMark programStartTime, pauseStart;
465 char ics_handle[MSG_SIZ];
466 int have_set_title = 0;
467
468 /* animateTraining preserves the state of appData.animate
469  * when Training mode is activated. This allows the
470  * response to be animated when appData.animate == TRUE and
471  * appData.animateDragging == TRUE.
472  */
473 Boolean animateTraining;
474
475 GameInfo gameInfo;
476
477 AppData appData;
478
479 Board boards[MAX_MOVES];
480 /* [HGM] Following 7 needed for accurate legality tests: */
481 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
482 signed char  initialRights[BOARD_FILES];
483 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
484 int   initialRulePlies, FENrulePlies;
485 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
486 int loadFlag = 0;
487 Boolean shuffleOpenings;
488 int mute; // mute all sounds
489
490 // [HGM] vari: next 12 to save and restore variations
491 #define MAX_VARIATIONS 10
492 int framePtr = MAX_MOVES-1; // points to free stack entry
493 int storedGames = 0;
494 int savedFirst[MAX_VARIATIONS];
495 int savedLast[MAX_VARIATIONS];
496 int savedFramePtr[MAX_VARIATIONS];
497 char *savedDetails[MAX_VARIATIONS];
498 ChessMove savedResult[MAX_VARIATIONS];
499
500 void PushTail P((int firstMove, int lastMove));
501 Boolean PopTail P((Boolean annotate));
502 void PushInner P((int firstMove, int lastMove));
503 void PopInner P((Boolean annotate));
504 void CleanupTail P((void));
505
506 ChessSquare  FIDEArray[2][BOARD_FILES] = {
507     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
508         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
509     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
510         BlackKing, BlackBishop, BlackKnight, BlackRook }
511 };
512
513 ChessSquare twoKingsArray[2][BOARD_FILES] = {
514     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
516     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517         BlackKing, BlackKing, BlackKnight, BlackRook }
518 };
519
520 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
521     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
522         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
523     { BlackRook, BlackMan, BlackBishop, BlackQueen,
524         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
525 };
526
527 ChessSquare SpartanArray[2][BOARD_FILES] = {
528     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
529         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
530     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
531         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
532 };
533
534 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
535     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
538         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
539 };
540
541 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
542     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
543         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
544     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
545         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
546 };
547
548 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
550         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
551     { BlackRook, BlackKnight, BlackMan, BlackFerz,
552         BlackKing, BlackMan, BlackKnight, BlackRook }
553 };
554
555
556 #if (BOARD_FILES>=10)
557 ChessSquare ShogiArray[2][BOARD_FILES] = {
558     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
559         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
560     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
561         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
562 };
563
564 ChessSquare XiangqiArray[2][BOARD_FILES] = {
565     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
566         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
567     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
568         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
569 };
570
571 ChessSquare CapablancaArray[2][BOARD_FILES] = {
572     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
573         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
574     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
575         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
576 };
577
578 ChessSquare GreatArray[2][BOARD_FILES] = {
579     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
580         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
581     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
582         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
583 };
584
585 ChessSquare JanusArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
587         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
588     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
589         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
590 };
591
592 ChessSquare GrandArray[2][BOARD_FILES] = {
593     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
594         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
595     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
596         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
597 };
598
599 #ifdef GOTHIC
600 ChessSquare GothicArray[2][BOARD_FILES] = {
601     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
602         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
603     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
604         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
605 };
606 #else // !GOTHIC
607 #define GothicArray CapablancaArray
608 #endif // !GOTHIC
609
610 #ifdef FALCON
611 ChessSquare FalconArray[2][BOARD_FILES] = {
612     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
613         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
614     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
615         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
616 };
617 #else // !FALCON
618 #define FalconArray CapablancaArray
619 #endif // !FALCON
620
621 #else // !(BOARD_FILES>=10)
622 #define XiangqiPosition FIDEArray
623 #define CapablancaArray FIDEArray
624 #define GothicArray FIDEArray
625 #define GreatArray FIDEArray
626 #endif // !(BOARD_FILES>=10)
627
628 #if (BOARD_FILES>=12)
629 ChessSquare CourierArray[2][BOARD_FILES] = {
630     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
631         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
632     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
633         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
634 };
635 #else // !(BOARD_FILES>=12)
636 #define CourierArray CapablancaArray
637 #endif // !(BOARD_FILES>=12)
638
639
640 Board initialPosition;
641
642
643 /* Convert str to a rating. Checks for special cases of "----",
644
645    "++++", etc. Also strips ()'s */
646 int
647 string_to_rating (char *str)
648 {
649   while(*str && !isdigit(*str)) ++str;
650   if (!*str)
651     return 0;   /* One of the special "no rating" cases */
652   else
653     return atoi(str);
654 }
655
656 void
657 ClearProgramStats ()
658 {
659     /* Init programStats */
660     programStats.movelist[0] = 0;
661     programStats.depth = 0;
662     programStats.nr_moves = 0;
663     programStats.moves_left = 0;
664     programStats.nodes = 0;
665     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
666     programStats.score = 0;
667     programStats.got_only_move = 0;
668     programStats.got_fail = 0;
669     programStats.line_is_book = 0;
670 }
671
672 void
673 CommonEngineInit ()
674 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
675     if (appData.firstPlaysBlack) {
676         first.twoMachinesColor = "black\n";
677         second.twoMachinesColor = "white\n";
678     } else {
679         first.twoMachinesColor = "white\n";
680         second.twoMachinesColor = "black\n";
681     }
682
683     first.other = &second;
684     second.other = &first;
685
686     { float norm = 1;
687         if(appData.timeOddsMode) {
688             norm = appData.timeOdds[0];
689             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
690         }
691         first.timeOdds  = appData.timeOdds[0]/norm;
692         second.timeOdds = appData.timeOdds[1]/norm;
693     }
694
695     if(programVersion) free(programVersion);
696     if (appData.noChessProgram) {
697         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
698         sprintf(programVersion, "%s", PACKAGE_STRING);
699     } else {
700       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
701       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
702       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
703     }
704 }
705
706 void
707 UnloadEngine (ChessProgramState *cps)
708 {
709         /* Kill off first chess program */
710         if (cps->isr != NULL)
711           RemoveInputSource(cps->isr);
712         cps->isr = NULL;
713
714         if (cps->pr != NoProc) {
715             ExitAnalyzeMode();
716             DoSleep( appData.delayBeforeQuit );
717             SendToProgram("quit\n", cps);
718             DoSleep( appData.delayAfterQuit );
719             DestroyChildProcess(cps->pr, cps->useSigterm);
720         }
721         cps->pr = NoProc;
722         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
723 }
724
725 void
726 ClearOptions (ChessProgramState *cps)
727 {
728     int i;
729     cps->nrOptions = cps->comboCnt = 0;
730     for(i=0; i<MAX_OPTIONS; i++) {
731         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
732         cps->option[i].textValue = 0;
733     }
734 }
735
736 char *engineNames[] = {
737 "first",
738 "second"
739 };
740
741 void
742 InitEngine (ChessProgramState *cps, int n)
743 {   // [HGM] all engine initialiation put in a function that does one engine
744
745     ClearOptions(cps);
746
747     cps->which = engineNames[n];
748     cps->maybeThinking = FALSE;
749     cps->pr = NoProc;
750     cps->isr = NULL;
751     cps->sendTime = 2;
752     cps->sendDrawOffers = 1;
753
754     cps->program = appData.chessProgram[n];
755     cps->host = appData.host[n];
756     cps->dir = appData.directory[n];
757     cps->initString = appData.engInitString[n];
758     cps->computerString = appData.computerString[n];
759     cps->useSigint  = TRUE;
760     cps->useSigterm = TRUE;
761     cps->reuse = appData.reuse[n];
762     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
763     cps->useSetboard = FALSE;
764     cps->useSAN = FALSE;
765     cps->usePing = FALSE;
766     cps->lastPing = 0;
767     cps->lastPong = 0;
768     cps->usePlayother = FALSE;
769     cps->useColors = TRUE;
770     cps->useUsermove = FALSE;
771     cps->sendICS = FALSE;
772     cps->sendName = appData.icsActive;
773     cps->sdKludge = FALSE;
774     cps->stKludge = FALSE;
775     TidyProgramName(cps->program, cps->host, cps->tidy);
776     cps->matchWins = 0;
777     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
778     cps->analysisSupport = 2; /* detect */
779     cps->analyzing = FALSE;
780     cps->initDone = FALSE;
781
782     /* New features added by Tord: */
783     cps->useFEN960 = FALSE;
784     cps->useOOCastle = TRUE;
785     /* End of new features added by Tord. */
786     cps->fenOverride  = appData.fenOverride[n];
787
788     /* [HGM] time odds: set factor for each machine */
789     cps->timeOdds  = appData.timeOdds[n];
790
791     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
792     cps->accumulateTC = appData.accumulateTC[n];
793     cps->maxNrOfSessions = 1;
794
795     /* [HGM] debug */
796     cps->debug = FALSE;
797
798     cps->supportsNPS = UNKNOWN;
799     cps->memSize = FALSE;
800     cps->maxCores = FALSE;
801     cps->egtFormats[0] = NULLCHAR;
802
803     /* [HGM] options */
804     cps->optionSettings  = appData.engOptions[n];
805
806     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
807     cps->isUCI = appData.isUCI[n]; /* [AS] */
808     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
809
810     if (appData.protocolVersion[n] > PROTOVER
811         || appData.protocolVersion[n] < 1)
812       {
813         char buf[MSG_SIZ];
814         int len;
815
816         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
817                        appData.protocolVersion[n]);
818         if( (len >= MSG_SIZ) && appData.debugMode )
819           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
820
821         DisplayFatalError(buf, 0, 2);
822       }
823     else
824       {
825         cps->protocolVersion = appData.protocolVersion[n];
826       }
827
828     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
829     ParseFeatures(appData.featureDefaults, cps);
830 }
831
832 ChessProgramState *savCps;
833
834 void
835 LoadEngine ()
836 {
837     int i;
838     if(WaitForEngine(savCps, LoadEngine)) return;
839     CommonEngineInit(); // recalculate time odds
840     if(gameInfo.variant != StringToVariant(appData.variant)) {
841         // we changed variant when loading the engine; this forces us to reset
842         Reset(TRUE, savCps != &first);
843         EditGameEvent(); // for consistency with other path, as Reset changes mode
844     }
845     InitChessProgram(savCps, FALSE);
846     SendToProgram("force\n", savCps);
847     DisplayMessage("", "");
848     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
849     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
850     ThawUI();
851     SetGNUMode();
852 }
853
854 void
855 ReplaceEngine (ChessProgramState *cps, int n)
856 {
857     EditGameEvent();
858     UnloadEngine(cps);
859     appData.noChessProgram = FALSE;
860     appData.clockMode = TRUE;
861     InitEngine(cps, n);
862     UpdateLogos(TRUE);
863     if(n) return; // only startup first engine immediately; second can wait
864     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
865     LoadEngine();
866 }
867
868 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
869 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
870
871 static char resetOptions[] = 
872         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
873         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
874         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
875
876 void
877 FloatToFront(char **list, char *engineLine)
878 {
879     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
880     int i=0;
881     if(appData.recentEngines <= 0) return;
882     TidyProgramName(engineLine, "localhost", tidy+1);
883     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
884     strncpy(buf+1, *list, MSG_SIZ-50);
885     if(p = strstr(buf, tidy)) { // tidy name appears in list
886         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
887         while(*p++ = *++q); // squeeze out
888     }
889     strcat(tidy, buf+1); // put list behind tidy name
890     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
891     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
892     ASSIGN(*list, tidy+1);
893 }
894
895 void
896 Load (ChessProgramState *cps, int i)
897 {
898     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
899     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
900         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
901         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
902         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
903         appData.firstProtocolVersion = PROTOVER;
904         ParseArgsFromString(buf);
905         SwapEngines(i);
906         ReplaceEngine(cps, i);
907         FloatToFront(&appData.recentEngineList, engineLine);
908         return;
909     }
910     p = engineName;
911     while(q = strchr(p, SLASH)) p = q+1;
912     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
913     if(engineDir[0] != NULLCHAR)
914         appData.directory[i] = engineDir;
915     else if(p != engineName) { // derive directory from engine path, when not given
916         p[-1] = 0;
917         appData.directory[i] = strdup(engineName);
918         p[-1] = SLASH;
919     } else appData.directory[i] = ".";
920     if(params[0]) {
921         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
922         snprintf(command, MSG_SIZ, "%s %s", p, params);
923         p = command;
924     }
925     appData.chessProgram[i] = strdup(p);
926     appData.isUCI[i] = isUCI;
927     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
928     appData.hasOwnBookUCI[i] = hasBook;
929     if(!nickName[0]) useNick = FALSE;
930     if(useNick) ASSIGN(appData.pgnName[i], nickName);
931     if(addToList) {
932         int len;
933         char quote;
934         q = firstChessProgramNames;
935         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
936         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
937         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
938                         quote, p, quote, appData.directory[i], 
939                         useNick ? " -fn \"" : "",
940                         useNick ? nickName : "",
941                         useNick ? "\"" : "",
942                         v1 ? " -firstProtocolVersion 1" : "",
943                         hasBook ? "" : " -fNoOwnBookUCI",
944                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
945                         storeVariant ? " -variant " : "",
946                         storeVariant ? VariantName(gameInfo.variant) : "");
947         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
948         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
949         if(q)   free(q);
950         FloatToFront(&appData.recentEngineList, buf);
951     }
952     ReplaceEngine(cps, i);
953 }
954
955 void
956 InitTimeControls ()
957 {
958     int matched, min, sec;
959     /*
960      * Parse timeControl resource
961      */
962     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
963                           appData.movesPerSession)) {
964         char buf[MSG_SIZ];
965         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
966         DisplayFatalError(buf, 0, 2);
967     }
968
969     /*
970      * Parse searchTime resource
971      */
972     if (*appData.searchTime != NULLCHAR) {
973         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
974         if (matched == 1) {
975             searchTime = min * 60;
976         } else if (matched == 2) {
977             searchTime = min * 60 + sec;
978         } else {
979             char buf[MSG_SIZ];
980             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
981             DisplayFatalError(buf, 0, 2);
982         }
983     }
984 }
985
986 void
987 InitBackEnd1 ()
988 {
989
990     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
991     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
992
993     GetTimeMark(&programStartTime);
994     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
995     appData.seedBase = random() + (random()<<15);
996     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
997
998     ClearProgramStats();
999     programStats.ok_to_send = 1;
1000     programStats.seen_stat = 0;
1001
1002     /*
1003      * Initialize game list
1004      */
1005     ListNew(&gameList);
1006
1007
1008     /*
1009      * Internet chess server status
1010      */
1011     if (appData.icsActive) {
1012         appData.matchMode = FALSE;
1013         appData.matchGames = 0;
1014 #if ZIPPY
1015         appData.noChessProgram = !appData.zippyPlay;
1016 #else
1017         appData.zippyPlay = FALSE;
1018         appData.zippyTalk = FALSE;
1019         appData.noChessProgram = TRUE;
1020 #endif
1021         if (*appData.icsHelper != NULLCHAR) {
1022             appData.useTelnet = TRUE;
1023             appData.telnetProgram = appData.icsHelper;
1024         }
1025     } else {
1026         appData.zippyTalk = appData.zippyPlay = FALSE;
1027     }
1028
1029     /* [AS] Initialize pv info list [HGM] and game state */
1030     {
1031         int i, j;
1032
1033         for( i=0; i<=framePtr; i++ ) {
1034             pvInfoList[i].depth = -1;
1035             boards[i][EP_STATUS] = EP_NONE;
1036             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1037         }
1038     }
1039
1040     InitTimeControls();
1041
1042     /* [AS] Adjudication threshold */
1043     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1044
1045     InitEngine(&first, 0);
1046     InitEngine(&second, 1);
1047     CommonEngineInit();
1048
1049     pairing.which = "pairing"; // pairing engine
1050     pairing.pr = NoProc;
1051     pairing.isr = NULL;
1052     pairing.program = appData.pairingEngine;
1053     pairing.host = "localhost";
1054     pairing.dir = ".";
1055
1056     if (appData.icsActive) {
1057         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1058     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1059         appData.clockMode = FALSE;
1060         first.sendTime = second.sendTime = 0;
1061     }
1062
1063 #if ZIPPY
1064     /* Override some settings from environment variables, for backward
1065        compatibility.  Unfortunately it's not feasible to have the env
1066        vars just set defaults, at least in xboard.  Ugh.
1067     */
1068     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1069       ZippyInit();
1070     }
1071 #endif
1072
1073     if (!appData.icsActive) {
1074       char buf[MSG_SIZ];
1075       int len;
1076
1077       /* Check for variants that are supported only in ICS mode,
1078          or not at all.  Some that are accepted here nevertheless
1079          have bugs; see comments below.
1080       */
1081       VariantClass variant = StringToVariant(appData.variant);
1082       switch (variant) {
1083       case VariantBughouse:     /* need four players and two boards */
1084       case VariantKriegspiel:   /* need to hide pieces and move details */
1085         /* case VariantFischeRandom: (Fabien: moved below) */
1086         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1087         if( (len >= MSG_SIZ) && appData.debugMode )
1088           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1089
1090         DisplayFatalError(buf, 0, 2);
1091         return;
1092
1093       case VariantUnknown:
1094       case VariantLoadable:
1095       case Variant29:
1096       case Variant30:
1097       case Variant31:
1098       case Variant32:
1099       case Variant33:
1100       case Variant34:
1101       case Variant35:
1102       case Variant36:
1103       default:
1104         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1105         if( (len >= MSG_SIZ) && appData.debugMode )
1106           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1107
1108         DisplayFatalError(buf, 0, 2);
1109         return;
1110
1111       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1112       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1113       case VariantGothic:     /* [HGM] should work */
1114       case VariantCapablanca: /* [HGM] should work */
1115       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1116       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1117       case VariantKnightmate: /* [HGM] should work */
1118       case VariantCylinder:   /* [HGM] untested */
1119       case VariantFalcon:     /* [HGM] untested */
1120       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1121                                  offboard interposition not understood */
1122       case VariantNormal:     /* definitely works! */
1123       case VariantWildCastle: /* pieces not automatically shuffled */
1124       case VariantNoCastle:   /* pieces not automatically shuffled */
1125       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1126       case VariantLosers:     /* should work except for win condition,
1127                                  and doesn't know captures are mandatory */
1128       case VariantSuicide:    /* should work except for win condition,
1129                                  and doesn't know captures are mandatory */
1130       case VariantGiveaway:   /* should work except for win condition,
1131                                  and doesn't know captures are mandatory */
1132       case VariantTwoKings:   /* should work */
1133       case VariantAtomic:     /* should work except for win condition */
1134       case Variant3Check:     /* should work except for win condition */
1135       case VariantShatranj:   /* should work except for all win conditions */
1136       case VariantMakruk:     /* should work except for draw countdown */
1137       case VariantBerolina:   /* might work if TestLegality is off */
1138       case VariantCapaRandom: /* should work */
1139       case VariantJanus:      /* should work */
1140       case VariantSuper:      /* experimental */
1141       case VariantGreat:      /* experimental, requires legality testing to be off */
1142       case VariantSChess:     /* S-Chess, should work */
1143       case VariantGrand:      /* should work */
1144       case VariantSpartan:    /* should work */
1145         break;
1146       }
1147     }
1148
1149 }
1150
1151 int
1152 NextIntegerFromString (char ** str, long * value)
1153 {
1154     int result = -1;
1155     char * s = *str;
1156
1157     while( *s == ' ' || *s == '\t' ) {
1158         s++;
1159     }
1160
1161     *value = 0;
1162
1163     if( *s >= '0' && *s <= '9' ) {
1164         while( *s >= '0' && *s <= '9' ) {
1165             *value = *value * 10 + (*s - '0');
1166             s++;
1167         }
1168
1169         result = 0;
1170     }
1171
1172     *str = s;
1173
1174     return result;
1175 }
1176
1177 int
1178 NextTimeControlFromString (char ** str, long * value)
1179 {
1180     long temp;
1181     int result = NextIntegerFromString( str, &temp );
1182
1183     if( result == 0 ) {
1184         *value = temp * 60; /* Minutes */
1185         if( **str == ':' ) {
1186             (*str)++;
1187             result = NextIntegerFromString( str, &temp );
1188             *value += temp; /* Seconds */
1189         }
1190     }
1191
1192     return result;
1193 }
1194
1195 int
1196 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1197 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1198     int result = -1, type = 0; long temp, temp2;
1199
1200     if(**str != ':') return -1; // old params remain in force!
1201     (*str)++;
1202     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1203     if( NextIntegerFromString( str, &temp ) ) return -1;
1204     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1205
1206     if(**str != '/') {
1207         /* time only: incremental or sudden-death time control */
1208         if(**str == '+') { /* increment follows; read it */
1209             (*str)++;
1210             if(**str == '!') type = *(*str)++; // Bronstein TC
1211             if(result = NextIntegerFromString( str, &temp2)) return -1;
1212             *inc = temp2 * 1000;
1213             if(**str == '.') { // read fraction of increment
1214                 char *start = ++(*str);
1215                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1216                 temp2 *= 1000;
1217                 while(start++ < *str) temp2 /= 10;
1218                 *inc += temp2;
1219             }
1220         } else *inc = 0;
1221         *moves = 0; *tc = temp * 1000; *incType = type;
1222         return 0;
1223     }
1224
1225     (*str)++; /* classical time control */
1226     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1227
1228     if(result == 0) {
1229         *moves = temp;
1230         *tc    = temp2 * 1000;
1231         *inc   = 0;
1232         *incType = type;
1233     }
1234     return result;
1235 }
1236
1237 int
1238 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1239 {   /* [HGM] get time to add from the multi-session time-control string */
1240     int incType, moves=1; /* kludge to force reading of first session */
1241     long time, increment;
1242     char *s = tcString;
1243
1244     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1245     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1246     do {
1247         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1248         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1249         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1250         if(movenr == -1) return time;    /* last move before new session     */
1251         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1252         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1253         if(!moves) return increment;     /* current session is incremental   */
1254         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1255     } while(movenr >= -1);               /* try again for next session       */
1256
1257     return 0; // no new time quota on this move
1258 }
1259
1260 int
1261 ParseTimeControl (char *tc, float ti, int mps)
1262 {
1263   long tc1;
1264   long tc2;
1265   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1266   int min, sec=0;
1267
1268   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1269   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1270       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1271   if(ti > 0) {
1272
1273     if(mps)
1274       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1275     else 
1276       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1277   } else {
1278     if(mps)
1279       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1280     else 
1281       snprintf(buf, MSG_SIZ, ":%s", mytc);
1282   }
1283   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1284   
1285   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1286     return FALSE;
1287   }
1288
1289   if( *tc == '/' ) {
1290     /* Parse second time control */
1291     tc++;
1292
1293     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1294       return FALSE;
1295     }
1296
1297     if( tc2 == 0 ) {
1298       return FALSE;
1299     }
1300
1301     timeControl_2 = tc2 * 1000;
1302   }
1303   else {
1304     timeControl_2 = 0;
1305   }
1306
1307   if( tc1 == 0 ) {
1308     return FALSE;
1309   }
1310
1311   timeControl = tc1 * 1000;
1312
1313   if (ti >= 0) {
1314     timeIncrement = ti * 1000;  /* convert to ms */
1315     movesPerSession = 0;
1316   } else {
1317     timeIncrement = 0;
1318     movesPerSession = mps;
1319   }
1320   return TRUE;
1321 }
1322
1323 void
1324 InitBackEnd2 ()
1325 {
1326     if (appData.debugMode) {
1327         fprintf(debugFP, "%s\n", programVersion);
1328     }
1329     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1330
1331     set_cont_sequence(appData.wrapContSeq);
1332     if (appData.matchGames > 0) {
1333         appData.matchMode = TRUE;
1334     } else if (appData.matchMode) {
1335         appData.matchGames = 1;
1336     }
1337     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1338         appData.matchGames = appData.sameColorGames;
1339     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1340         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1341         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1342     }
1343     Reset(TRUE, FALSE);
1344     if (appData.noChessProgram || first.protocolVersion == 1) {
1345       InitBackEnd3();
1346     } else {
1347       /* kludge: allow timeout for initial "feature" commands */
1348       FreezeUI();
1349       DisplayMessage("", _("Starting chess program"));
1350       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1351     }
1352 }
1353
1354 int
1355 CalculateIndex (int index, int gameNr)
1356 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1357     int res;
1358     if(index > 0) return index; // fixed nmber
1359     if(index == 0) return 1;
1360     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1361     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1362     return res;
1363 }
1364
1365 int
1366 LoadGameOrPosition (int gameNr)
1367 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1368     if (*appData.loadGameFile != NULLCHAR) {
1369         if (!LoadGameFromFile(appData.loadGameFile,
1370                 CalculateIndex(appData.loadGameIndex, gameNr),
1371                               appData.loadGameFile, FALSE)) {
1372             DisplayFatalError(_("Bad game file"), 0, 1);
1373             return 0;
1374         }
1375     } else if (*appData.loadPositionFile != NULLCHAR) {
1376         if (!LoadPositionFromFile(appData.loadPositionFile,
1377                 CalculateIndex(appData.loadPositionIndex, gameNr),
1378                                   appData.loadPositionFile)) {
1379             DisplayFatalError(_("Bad position file"), 0, 1);
1380             return 0;
1381         }
1382     }
1383     return 1;
1384 }
1385
1386 void
1387 ReserveGame (int gameNr, char resChar)
1388 {
1389     FILE *tf = fopen(appData.tourneyFile, "r+");
1390     char *p, *q, c, buf[MSG_SIZ];
1391     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1392     safeStrCpy(buf, lastMsg, MSG_SIZ);
1393     DisplayMessage(_("Pick new game"), "");
1394     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1395     ParseArgsFromFile(tf);
1396     p = q = appData.results;
1397     if(appData.debugMode) {
1398       char *r = appData.participants;
1399       fprintf(debugFP, "results = '%s'\n", p);
1400       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1401       fprintf(debugFP, "\n");
1402     }
1403     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1404     nextGame = q - p;
1405     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1406     safeStrCpy(q, p, strlen(p) + 2);
1407     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1408     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1409     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1410         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1411         q[nextGame] = '*';
1412     }
1413     fseek(tf, -(strlen(p)+4), SEEK_END);
1414     c = fgetc(tf);
1415     if(c != '"') // depending on DOS or Unix line endings we can be one off
1416          fseek(tf, -(strlen(p)+2), SEEK_END);
1417     else fseek(tf, -(strlen(p)+3), SEEK_END);
1418     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1419     DisplayMessage(buf, "");
1420     free(p); appData.results = q;
1421     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1422        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1423       int round = appData.defaultMatchGames * appData.tourneyType;
1424       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1425          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1426         UnloadEngine(&first);  // next game belongs to other pairing;
1427         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1428     }
1429     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d, procs=(%x,%x)\n", nextGame, gameNr, first.pr, second.pr);
1430 }
1431
1432 void
1433 MatchEvent (int mode)
1434 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1435         int dummy;
1436         if(matchMode) { // already in match mode: switch it off
1437             abortMatch = TRUE;
1438             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1439             return;
1440         }
1441 //      if(gameMode != BeginningOfGame) {
1442 //          DisplayError(_("You can only start a match from the initial position."), 0);
1443 //          return;
1444 //      }
1445         abortMatch = FALSE;
1446         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1447         /* Set up machine vs. machine match */
1448         nextGame = 0;
1449         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1450         if(appData.tourneyFile[0]) {
1451             ReserveGame(-1, 0);
1452             if(nextGame > appData.matchGames) {
1453                 char buf[MSG_SIZ];
1454                 if(strchr(appData.results, '*') == NULL) {
1455                     FILE *f;
1456                     appData.tourneyCycles++;
1457                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1458                         fclose(f);
1459                         NextTourneyGame(-1, &dummy);
1460                         ReserveGame(-1, 0);
1461                         if(nextGame <= appData.matchGames) {
1462                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1463                             matchMode = mode;
1464                             ScheduleDelayedEvent(NextMatchGame, 10000);
1465                             return;
1466                         }
1467                     }
1468                 }
1469                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1470                 DisplayError(buf, 0);
1471                 appData.tourneyFile[0] = 0;
1472                 return;
1473             }
1474         } else
1475         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1476             DisplayFatalError(_("Can't have a match with no chess programs"),
1477                               0, 2);
1478             return;
1479         }
1480         matchMode = mode;
1481         matchGame = roundNr = 1;
1482         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1483         NextMatchGame();
1484 }
1485
1486 void
1487 InitBackEnd3 P((void))
1488 {
1489     GameMode initialMode;
1490     char buf[MSG_SIZ];
1491     int err, len;
1492
1493     InitChessProgram(&first, startedFromSetupPosition);
1494
1495     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1496         free(programVersion);
1497         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1498         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1499         FloatToFront(&appData.recentEngineList, appData.firstChessProgram);
1500     }
1501
1502     if (appData.icsActive) {
1503 #ifdef WIN32
1504         /* [DM] Make a console window if needed [HGM] merged ifs */
1505         ConsoleCreate();
1506 #endif
1507         err = establish();
1508         if (err != 0)
1509           {
1510             if (*appData.icsCommPort != NULLCHAR)
1511               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1512                              appData.icsCommPort);
1513             else
1514               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1515                         appData.icsHost, appData.icsPort);
1516
1517             if( (len >= MSG_SIZ) && appData.debugMode )
1518               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1519
1520             DisplayFatalError(buf, err, 1);
1521             return;
1522         }
1523         SetICSMode();
1524         telnetISR =
1525           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1526         fromUserISR =
1527           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1528         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1529             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1530     } else if (appData.noChessProgram) {
1531         SetNCPMode();
1532     } else {
1533         SetGNUMode();
1534     }
1535
1536     if (*appData.cmailGameName != NULLCHAR) {
1537         SetCmailMode();
1538         OpenLoopback(&cmailPR);
1539         cmailISR =
1540           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1541     }
1542
1543     ThawUI();
1544     DisplayMessage("", "");
1545     if (StrCaseCmp(appData.initialMode, "") == 0) {
1546       initialMode = BeginningOfGame;
1547       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1548         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1549         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1550         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1551         ModeHighlight();
1552       }
1553     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1554       initialMode = TwoMachinesPlay;
1555     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1556       initialMode = AnalyzeFile;
1557     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1558       initialMode = AnalyzeMode;
1559     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1560       initialMode = MachinePlaysWhite;
1561     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1562       initialMode = MachinePlaysBlack;
1563     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1564       initialMode = EditGame;
1565     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1566       initialMode = EditPosition;
1567     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1568       initialMode = Training;
1569     } else {
1570       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1571       if( (len >= MSG_SIZ) && appData.debugMode )
1572         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1573
1574       DisplayFatalError(buf, 0, 2);
1575       return;
1576     }
1577
1578     if (appData.matchMode) {
1579         if(appData.tourneyFile[0]) { // start tourney from command line
1580             FILE *f;
1581             if(f = fopen(appData.tourneyFile, "r")) {
1582                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1583                 fclose(f);
1584                 appData.clockMode = TRUE;
1585                 SetGNUMode();
1586             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1587         }
1588         MatchEvent(TRUE);
1589     } else if (*appData.cmailGameName != NULLCHAR) {
1590         /* Set up cmail mode */
1591         ReloadCmailMsgEvent(TRUE);
1592     } else {
1593         /* Set up other modes */
1594         if (initialMode == AnalyzeFile) {
1595           if (*appData.loadGameFile == NULLCHAR) {
1596             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1597             return;
1598           }
1599         }
1600         if (*appData.loadGameFile != NULLCHAR) {
1601             (void) LoadGameFromFile(appData.loadGameFile,
1602                                     appData.loadGameIndex,
1603                                     appData.loadGameFile, TRUE);
1604         } else if (*appData.loadPositionFile != NULLCHAR) {
1605             (void) LoadPositionFromFile(appData.loadPositionFile,
1606                                         appData.loadPositionIndex,
1607                                         appData.loadPositionFile);
1608             /* [HGM] try to make self-starting even after FEN load */
1609             /* to allow automatic setup of fairy variants with wtm */
1610             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1611                 gameMode = BeginningOfGame;
1612                 setboardSpoiledMachineBlack = 1;
1613             }
1614             /* [HGM] loadPos: make that every new game uses the setup */
1615             /* from file as long as we do not switch variant          */
1616             if(!blackPlaysFirst) {
1617                 startedFromPositionFile = TRUE;
1618                 CopyBoard(filePosition, boards[0]);
1619             }
1620         }
1621         if (initialMode == AnalyzeMode) {
1622           if (appData.noChessProgram) {
1623             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1624             return;
1625           }
1626           if (appData.icsActive) {
1627             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1628             return;
1629           }
1630           AnalyzeModeEvent();
1631         } else if (initialMode == AnalyzeFile) {
1632           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1633           ShowThinkingEvent();
1634           AnalyzeFileEvent();
1635           AnalysisPeriodicEvent(1);
1636         } else if (initialMode == MachinePlaysWhite) {
1637           if (appData.noChessProgram) {
1638             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1639                               0, 2);
1640             return;
1641           }
1642           if (appData.icsActive) {
1643             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1644                               0, 2);
1645             return;
1646           }
1647           MachineWhiteEvent();
1648         } else if (initialMode == MachinePlaysBlack) {
1649           if (appData.noChessProgram) {
1650             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1651                               0, 2);
1652             return;
1653           }
1654           if (appData.icsActive) {
1655             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1656                               0, 2);
1657             return;
1658           }
1659           MachineBlackEvent();
1660         } else if (initialMode == TwoMachinesPlay) {
1661           if (appData.noChessProgram) {
1662             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1663                               0, 2);
1664             return;
1665           }
1666           if (appData.icsActive) {
1667             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1668                               0, 2);
1669             return;
1670           }
1671           TwoMachinesEvent();
1672         } else if (initialMode == EditGame) {
1673           EditGameEvent();
1674         } else if (initialMode == EditPosition) {
1675           EditPositionEvent();
1676         } else if (initialMode == Training) {
1677           if (*appData.loadGameFile == NULLCHAR) {
1678             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1679             return;
1680           }
1681           TrainingEvent();
1682         }
1683     }
1684 }
1685
1686 void
1687 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1688 {
1689     DisplayBook(current+1);
1690
1691     MoveHistorySet( movelist, first, last, current, pvInfoList );
1692
1693     EvalGraphSet( first, last, current, pvInfoList );
1694
1695     MakeEngineOutputTitle();
1696 }
1697
1698 /*
1699  * Establish will establish a contact to a remote host.port.
1700  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1701  *  used to talk to the host.
1702  * Returns 0 if okay, error code if not.
1703  */
1704 int
1705 establish ()
1706 {
1707     char buf[MSG_SIZ];
1708
1709     if (*appData.icsCommPort != NULLCHAR) {
1710         /* Talk to the host through a serial comm port */
1711         return OpenCommPort(appData.icsCommPort, &icsPR);
1712
1713     } else if (*appData.gateway != NULLCHAR) {
1714         if (*appData.remoteShell == NULLCHAR) {
1715             /* Use the rcmd protocol to run telnet program on a gateway host */
1716             snprintf(buf, sizeof(buf), "%s %s %s",
1717                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1718             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1719
1720         } else {
1721             /* Use the rsh program to run telnet program on a gateway host */
1722             if (*appData.remoteUser == NULLCHAR) {
1723                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1724                         appData.gateway, appData.telnetProgram,
1725                         appData.icsHost, appData.icsPort);
1726             } else {
1727                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1728                         appData.remoteShell, appData.gateway,
1729                         appData.remoteUser, appData.telnetProgram,
1730                         appData.icsHost, appData.icsPort);
1731             }
1732             return StartChildProcess(buf, "", &icsPR);
1733
1734         }
1735     } else if (appData.useTelnet) {
1736         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1737
1738     } else {
1739         /* TCP socket interface differs somewhat between
1740            Unix and NT; handle details in the front end.
1741            */
1742         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1743     }
1744 }
1745
1746 void
1747 EscapeExpand (char *p, char *q)
1748 {       // [HGM] initstring: routine to shape up string arguments
1749         while(*p++ = *q++) if(p[-1] == '\\')
1750             switch(*q++) {
1751                 case 'n': p[-1] = '\n'; break;
1752                 case 'r': p[-1] = '\r'; break;
1753                 case 't': p[-1] = '\t'; break;
1754                 case '\\': p[-1] = '\\'; break;
1755                 case 0: *p = 0; return;
1756                 default: p[-1] = q[-1]; break;
1757             }
1758 }
1759
1760 void
1761 show_bytes (FILE *fp, char *buf, int count)
1762 {
1763     while (count--) {
1764         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1765             fprintf(fp, "\\%03o", *buf & 0xff);
1766         } else {
1767             putc(*buf, fp);
1768         }
1769         buf++;
1770     }
1771     fflush(fp);
1772 }
1773
1774 /* Returns an errno value */
1775 int
1776 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1777 {
1778     char buf[8192], *p, *q, *buflim;
1779     int left, newcount, outcount;
1780
1781     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1782         *appData.gateway != NULLCHAR) {
1783         if (appData.debugMode) {
1784             fprintf(debugFP, ">ICS: ");
1785             show_bytes(debugFP, message, count);
1786             fprintf(debugFP, "\n");
1787         }
1788         return OutputToProcess(pr, message, count, outError);
1789     }
1790
1791     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1792     p = message;
1793     q = buf;
1794     left = count;
1795     newcount = 0;
1796     while (left) {
1797         if (q >= buflim) {
1798             if (appData.debugMode) {
1799                 fprintf(debugFP, ">ICS: ");
1800                 show_bytes(debugFP, buf, newcount);
1801                 fprintf(debugFP, "\n");
1802             }
1803             outcount = OutputToProcess(pr, buf, newcount, outError);
1804             if (outcount < newcount) return -1; /* to be sure */
1805             q = buf;
1806             newcount = 0;
1807         }
1808         if (*p == '\n') {
1809             *q++ = '\r';
1810             newcount++;
1811         } else if (((unsigned char) *p) == TN_IAC) {
1812             *q++ = (char) TN_IAC;
1813             newcount ++;
1814         }
1815         *q++ = *p++;
1816         newcount++;
1817         left--;
1818     }
1819     if (appData.debugMode) {
1820         fprintf(debugFP, ">ICS: ");
1821         show_bytes(debugFP, buf, newcount);
1822         fprintf(debugFP, "\n");
1823     }
1824     outcount = OutputToProcess(pr, buf, newcount, outError);
1825     if (outcount < newcount) return -1; /* to be sure */
1826     return count;
1827 }
1828
1829 void
1830 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1831 {
1832     int outError, outCount;
1833     static int gotEof = 0;
1834
1835     /* Pass data read from player on to ICS */
1836     if (count > 0) {
1837         gotEof = 0;
1838         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1839         if (outCount < count) {
1840             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1841         }
1842     } else if (count < 0) {
1843         RemoveInputSource(isr);
1844         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1845     } else if (gotEof++ > 0) {
1846         RemoveInputSource(isr);
1847         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1848     }
1849 }
1850
1851 void
1852 KeepAlive ()
1853 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1854     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1855     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1856     SendToICS("date\n");
1857     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1858 }
1859
1860 /* added routine for printf style output to ics */
1861 void
1862 ics_printf (char *format, ...)
1863 {
1864     char buffer[MSG_SIZ];
1865     va_list args;
1866
1867     va_start(args, format);
1868     vsnprintf(buffer, sizeof(buffer), format, args);
1869     buffer[sizeof(buffer)-1] = '\0';
1870     SendToICS(buffer);
1871     va_end(args);
1872 }
1873
1874 void
1875 SendToICS (char *s)
1876 {
1877     int count, outCount, outError;
1878
1879     if (icsPR == NoProc) return;
1880
1881     count = strlen(s);
1882     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1883     if (outCount < count) {
1884         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1885     }
1886 }
1887
1888 /* This is used for sending logon scripts to the ICS. Sending
1889    without a delay causes problems when using timestamp on ICC
1890    (at least on my machine). */
1891 void
1892 SendToICSDelayed (char *s, long msdelay)
1893 {
1894     int count, outCount, outError;
1895
1896     if (icsPR == NoProc) return;
1897
1898     count = strlen(s);
1899     if (appData.debugMode) {
1900         fprintf(debugFP, ">ICS: ");
1901         show_bytes(debugFP, s, count);
1902         fprintf(debugFP, "\n");
1903     }
1904     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1905                                       msdelay);
1906     if (outCount < count) {
1907         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1908     }
1909 }
1910
1911
1912 /* Remove all highlighting escape sequences in s
1913    Also deletes any suffix starting with '('
1914    */
1915 char *
1916 StripHighlightAndTitle (char *s)
1917 {
1918     static char retbuf[MSG_SIZ];
1919     char *p = retbuf;
1920
1921     while (*s != NULLCHAR) {
1922         while (*s == '\033') {
1923             while (*s != NULLCHAR && !isalpha(*s)) s++;
1924             if (*s != NULLCHAR) s++;
1925         }
1926         while (*s != NULLCHAR && *s != '\033') {
1927             if (*s == '(' || *s == '[') {
1928                 *p = NULLCHAR;
1929                 return retbuf;
1930             }
1931             *p++ = *s++;
1932         }
1933     }
1934     *p = NULLCHAR;
1935     return retbuf;
1936 }
1937
1938 /* Remove all highlighting escape sequences in s */
1939 char *
1940 StripHighlight (char *s)
1941 {
1942     static char retbuf[MSG_SIZ];
1943     char *p = retbuf;
1944
1945     while (*s != NULLCHAR) {
1946         while (*s == '\033') {
1947             while (*s != NULLCHAR && !isalpha(*s)) s++;
1948             if (*s != NULLCHAR) s++;
1949         }
1950         while (*s != NULLCHAR && *s != '\033') {
1951             *p++ = *s++;
1952         }
1953     }
1954     *p = NULLCHAR;
1955     return retbuf;
1956 }
1957
1958 char *variantNames[] = VARIANT_NAMES;
1959 char *
1960 VariantName (VariantClass v)
1961 {
1962     return variantNames[v];
1963 }
1964
1965
1966 /* Identify a variant from the strings the chess servers use or the
1967    PGN Variant tag names we use. */
1968 VariantClass
1969 StringToVariant (char *e)
1970 {
1971     char *p;
1972     int wnum = -1;
1973     VariantClass v = VariantNormal;
1974     int i, found = FALSE;
1975     char buf[MSG_SIZ];
1976     int len;
1977
1978     if (!e) return v;
1979
1980     /* [HGM] skip over optional board-size prefixes */
1981     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1982         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1983         while( *e++ != '_');
1984     }
1985
1986     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1987         v = VariantNormal;
1988         found = TRUE;
1989     } else
1990     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1991       if (StrCaseStr(e, variantNames[i])) {
1992         v = (VariantClass) i;
1993         found = TRUE;
1994         break;
1995       }
1996     }
1997
1998     if (!found) {
1999       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2000           || StrCaseStr(e, "wild/fr")
2001           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2002         v = VariantFischeRandom;
2003       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2004                  (i = 1, p = StrCaseStr(e, "w"))) {
2005         p += i;
2006         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2007         if (isdigit(*p)) {
2008           wnum = atoi(p);
2009         } else {
2010           wnum = -1;
2011         }
2012         switch (wnum) {
2013         case 0: /* FICS only, actually */
2014         case 1:
2015           /* Castling legal even if K starts on d-file */
2016           v = VariantWildCastle;
2017           break;
2018         case 2:
2019         case 3:
2020         case 4:
2021           /* Castling illegal even if K & R happen to start in
2022              normal positions. */
2023           v = VariantNoCastle;
2024           break;
2025         case 5:
2026         case 7:
2027         case 8:
2028         case 10:
2029         case 11:
2030         case 12:
2031         case 13:
2032         case 14:
2033         case 15:
2034         case 18:
2035         case 19:
2036           /* Castling legal iff K & R start in normal positions */
2037           v = VariantNormal;
2038           break;
2039         case 6:
2040         case 20:
2041         case 21:
2042           /* Special wilds for position setup; unclear what to do here */
2043           v = VariantLoadable;
2044           break;
2045         case 9:
2046           /* Bizarre ICC game */
2047           v = VariantTwoKings;
2048           break;
2049         case 16:
2050           v = VariantKriegspiel;
2051           break;
2052         case 17:
2053           v = VariantLosers;
2054           break;
2055         case 22:
2056           v = VariantFischeRandom;
2057           break;
2058         case 23:
2059           v = VariantCrazyhouse;
2060           break;
2061         case 24:
2062           v = VariantBughouse;
2063           break;
2064         case 25:
2065           v = Variant3Check;
2066           break;
2067         case 26:
2068           /* Not quite the same as FICS suicide! */
2069           v = VariantGiveaway;
2070           break;
2071         case 27:
2072           v = VariantAtomic;
2073           break;
2074         case 28:
2075           v = VariantShatranj;
2076           break;
2077
2078         /* Temporary names for future ICC types.  The name *will* change in
2079            the next xboard/WinBoard release after ICC defines it. */
2080         case 29:
2081           v = Variant29;
2082           break;
2083         case 30:
2084           v = Variant30;
2085           break;
2086         case 31:
2087           v = Variant31;
2088           break;
2089         case 32:
2090           v = Variant32;
2091           break;
2092         case 33:
2093           v = Variant33;
2094           break;
2095         case 34:
2096           v = Variant34;
2097           break;
2098         case 35:
2099           v = Variant35;
2100           break;
2101         case 36:
2102           v = Variant36;
2103           break;
2104         case 37:
2105           v = VariantShogi;
2106           break;
2107         case 38:
2108           v = VariantXiangqi;
2109           break;
2110         case 39:
2111           v = VariantCourier;
2112           break;
2113         case 40:
2114           v = VariantGothic;
2115           break;
2116         case 41:
2117           v = VariantCapablanca;
2118           break;
2119         case 42:
2120           v = VariantKnightmate;
2121           break;
2122         case 43:
2123           v = VariantFairy;
2124           break;
2125         case 44:
2126           v = VariantCylinder;
2127           break;
2128         case 45:
2129           v = VariantFalcon;
2130           break;
2131         case 46:
2132           v = VariantCapaRandom;
2133           break;
2134         case 47:
2135           v = VariantBerolina;
2136           break;
2137         case 48:
2138           v = VariantJanus;
2139           break;
2140         case 49:
2141           v = VariantSuper;
2142           break;
2143         case 50:
2144           v = VariantGreat;
2145           break;
2146         case -1:
2147           /* Found "wild" or "w" in the string but no number;
2148              must assume it's normal chess. */
2149           v = VariantNormal;
2150           break;
2151         default:
2152           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2153           if( (len >= MSG_SIZ) && appData.debugMode )
2154             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2155
2156           DisplayError(buf, 0);
2157           v = VariantUnknown;
2158           break;
2159         }
2160       }
2161     }
2162     if (appData.debugMode) {
2163       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2164               e, wnum, VariantName(v));
2165     }
2166     return v;
2167 }
2168
2169 static int leftover_start = 0, leftover_len = 0;
2170 char star_match[STAR_MATCH_N][MSG_SIZ];
2171
2172 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2173    advance *index beyond it, and set leftover_start to the new value of
2174    *index; else return FALSE.  If pattern contains the character '*', it
2175    matches any sequence of characters not containing '\r', '\n', or the
2176    character following the '*' (if any), and the matched sequence(s) are
2177    copied into star_match.
2178    */
2179 int
2180 looking_at ( char *buf, int *index, char *pattern)
2181 {
2182     char *bufp = &buf[*index], *patternp = pattern;
2183     int star_count = 0;
2184     char *matchp = star_match[0];
2185
2186     for (;;) {
2187         if (*patternp == NULLCHAR) {
2188             *index = leftover_start = bufp - buf;
2189             *matchp = NULLCHAR;
2190             return TRUE;
2191         }
2192         if (*bufp == NULLCHAR) return FALSE;
2193         if (*patternp == '*') {
2194             if (*bufp == *(patternp + 1)) {
2195                 *matchp = NULLCHAR;
2196                 matchp = star_match[++star_count];
2197                 patternp += 2;
2198                 bufp++;
2199                 continue;
2200             } else if (*bufp == '\n' || *bufp == '\r') {
2201                 patternp++;
2202                 if (*patternp == NULLCHAR)
2203                   continue;
2204                 else
2205                   return FALSE;
2206             } else {
2207                 *matchp++ = *bufp++;
2208                 continue;
2209             }
2210         }
2211         if (*patternp != *bufp) return FALSE;
2212         patternp++;
2213         bufp++;
2214     }
2215 }
2216
2217 void
2218 SendToPlayer (char *data, int length)
2219 {
2220     int error, outCount;
2221     outCount = OutputToProcess(NoProc, data, length, &error);
2222     if (outCount < length) {
2223         DisplayFatalError(_("Error writing to display"), error, 1);
2224     }
2225 }
2226
2227 void
2228 PackHolding (char packed[], char *holding)
2229 {
2230     char *p = holding;
2231     char *q = packed;
2232     int runlength = 0;
2233     int curr = 9999;
2234     do {
2235         if (*p == curr) {
2236             runlength++;
2237         } else {
2238             switch (runlength) {
2239               case 0:
2240                 break;
2241               case 1:
2242                 *q++ = curr;
2243                 break;
2244               case 2:
2245                 *q++ = curr;
2246                 *q++ = curr;
2247                 break;
2248               default:
2249                 sprintf(q, "%d", runlength);
2250                 while (*q) q++;
2251                 *q++ = curr;
2252                 break;
2253             }
2254             runlength = 1;
2255             curr = *p;
2256         }
2257     } while (*p++);
2258     *q = NULLCHAR;
2259 }
2260
2261 /* Telnet protocol requests from the front end */
2262 void
2263 TelnetRequest (unsigned char ddww, unsigned char option)
2264 {
2265     unsigned char msg[3];
2266     int outCount, outError;
2267
2268     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2269
2270     if (appData.debugMode) {
2271         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2272         switch (ddww) {
2273           case TN_DO:
2274             ddwwStr = "DO";
2275             break;
2276           case TN_DONT:
2277             ddwwStr = "DONT";
2278             break;
2279           case TN_WILL:
2280             ddwwStr = "WILL";
2281             break;
2282           case TN_WONT:
2283             ddwwStr = "WONT";
2284             break;
2285           default:
2286             ddwwStr = buf1;
2287             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2288             break;
2289         }
2290         switch (option) {
2291           case TN_ECHO:
2292             optionStr = "ECHO";
2293             break;
2294           default:
2295             optionStr = buf2;
2296             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2297             break;
2298         }
2299         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2300     }
2301     msg[0] = TN_IAC;
2302     msg[1] = ddww;
2303     msg[2] = option;
2304     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2305     if (outCount < 3) {
2306         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2307     }
2308 }
2309
2310 void
2311 DoEcho ()
2312 {
2313     if (!appData.icsActive) return;
2314     TelnetRequest(TN_DO, TN_ECHO);
2315 }
2316
2317 void
2318 DontEcho ()
2319 {
2320     if (!appData.icsActive) return;
2321     TelnetRequest(TN_DONT, TN_ECHO);
2322 }
2323
2324 void
2325 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2326 {
2327     /* put the holdings sent to us by the server on the board holdings area */
2328     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2329     char p;
2330     ChessSquare piece;
2331
2332     if(gameInfo.holdingsWidth < 2)  return;
2333     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2334         return; // prevent overwriting by pre-board holdings
2335
2336     if( (int)lowestPiece >= BlackPawn ) {
2337         holdingsColumn = 0;
2338         countsColumn = 1;
2339         holdingsStartRow = BOARD_HEIGHT-1;
2340         direction = -1;
2341     } else {
2342         holdingsColumn = BOARD_WIDTH-1;
2343         countsColumn = BOARD_WIDTH-2;
2344         holdingsStartRow = 0;
2345         direction = 1;
2346     }
2347
2348     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2349         board[i][holdingsColumn] = EmptySquare;
2350         board[i][countsColumn]   = (ChessSquare) 0;
2351     }
2352     while( (p=*holdings++) != NULLCHAR ) {
2353         piece = CharToPiece( ToUpper(p) );
2354         if(piece == EmptySquare) continue;
2355         /*j = (int) piece - (int) WhitePawn;*/
2356         j = PieceToNumber(piece);
2357         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2358         if(j < 0) continue;               /* should not happen */
2359         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2360         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2361         board[holdingsStartRow+j*direction][countsColumn]++;
2362     }
2363 }
2364
2365
2366 void
2367 VariantSwitch (Board board, VariantClass newVariant)
2368 {
2369    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2370    static Board oldBoard;
2371
2372    startedFromPositionFile = FALSE;
2373    if(gameInfo.variant == newVariant) return;
2374
2375    /* [HGM] This routine is called each time an assignment is made to
2376     * gameInfo.variant during a game, to make sure the board sizes
2377     * are set to match the new variant. If that means adding or deleting
2378     * holdings, we shift the playing board accordingly
2379     * This kludge is needed because in ICS observe mode, we get boards
2380     * of an ongoing game without knowing the variant, and learn about the
2381     * latter only later. This can be because of the move list we requested,
2382     * in which case the game history is refilled from the beginning anyway,
2383     * but also when receiving holdings of a crazyhouse game. In the latter
2384     * case we want to add those holdings to the already received position.
2385     */
2386
2387
2388    if (appData.debugMode) {
2389      fprintf(debugFP, "Switch board from %s to %s\n",
2390              VariantName(gameInfo.variant), VariantName(newVariant));
2391      setbuf(debugFP, NULL);
2392    }
2393    shuffleOpenings = 0;       /* [HGM] shuffle */
2394    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2395    switch(newVariant)
2396      {
2397      case VariantShogi:
2398        newWidth = 9;  newHeight = 9;
2399        gameInfo.holdingsSize = 7;
2400      case VariantBughouse:
2401      case VariantCrazyhouse:
2402        newHoldingsWidth = 2; break;
2403      case VariantGreat:
2404        newWidth = 10;
2405      case VariantSuper:
2406        newHoldingsWidth = 2;
2407        gameInfo.holdingsSize = 8;
2408        break;
2409      case VariantGothic:
2410      case VariantCapablanca:
2411      case VariantCapaRandom:
2412        newWidth = 10;
2413      default:
2414        newHoldingsWidth = gameInfo.holdingsSize = 0;
2415      };
2416
2417    if(newWidth  != gameInfo.boardWidth  ||
2418       newHeight != gameInfo.boardHeight ||
2419       newHoldingsWidth != gameInfo.holdingsWidth ) {
2420
2421      /* shift position to new playing area, if needed */
2422      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2423        for(i=0; i<BOARD_HEIGHT; i++)
2424          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2425            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2426              board[i][j];
2427        for(i=0; i<newHeight; i++) {
2428          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2429          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2430        }
2431      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2432        for(i=0; i<BOARD_HEIGHT; i++)
2433          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2434            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2435              board[i][j];
2436      }
2437      gameInfo.boardWidth  = newWidth;
2438      gameInfo.boardHeight = newHeight;
2439      gameInfo.holdingsWidth = newHoldingsWidth;
2440      gameInfo.variant = newVariant;
2441      InitDrawingSizes(-2, 0);
2442    } else gameInfo.variant = newVariant;
2443    CopyBoard(oldBoard, board);   // remember correctly formatted board
2444      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2445    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2446 }
2447
2448 static int loggedOn = FALSE;
2449
2450 /*-- Game start info cache: --*/
2451 int gs_gamenum;
2452 char gs_kind[MSG_SIZ];
2453 static char player1Name[128] = "";
2454 static char player2Name[128] = "";
2455 static char cont_seq[] = "\n\\   ";
2456 static int player1Rating = -1;
2457 static int player2Rating = -1;
2458 /*----------------------------*/
2459
2460 ColorClass curColor = ColorNormal;
2461 int suppressKibitz = 0;
2462
2463 // [HGM] seekgraph
2464 Boolean soughtPending = FALSE;
2465 Boolean seekGraphUp;
2466 #define MAX_SEEK_ADS 200
2467 #define SQUARE 0x80
2468 char *seekAdList[MAX_SEEK_ADS];
2469 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2470 float tcList[MAX_SEEK_ADS];
2471 char colorList[MAX_SEEK_ADS];
2472 int nrOfSeekAds = 0;
2473 int minRating = 1010, maxRating = 2800;
2474 int hMargin = 10, vMargin = 20, h, w;
2475 extern int squareSize, lineGap;
2476
2477 void
2478 PlotSeekAd (int i)
2479 {
2480         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2481         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2482         if(r < minRating+100 && r >=0 ) r = minRating+100;
2483         if(r > maxRating) r = maxRating;
2484         if(tc < 1.) tc = 1.;
2485         if(tc > 95.) tc = 95.;
2486         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2487         y = ((double)r - minRating)/(maxRating - minRating)
2488             * (h-vMargin-squareSize/8-1) + vMargin;
2489         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2490         if(strstr(seekAdList[i], " u ")) color = 1;
2491         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2492            !strstr(seekAdList[i], "bullet") &&
2493            !strstr(seekAdList[i], "blitz") &&
2494            !strstr(seekAdList[i], "standard") ) color = 2;
2495         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2496         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2497 }
2498
2499 void
2500 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2501 {
2502         char buf[MSG_SIZ], *ext = "";
2503         VariantClass v = StringToVariant(type);
2504         if(strstr(type, "wild")) {
2505             ext = type + 4; // append wild number
2506             if(v == VariantFischeRandom) type = "chess960"; else
2507             if(v == VariantLoadable) type = "setup"; else
2508             type = VariantName(v);
2509         }
2510         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2511         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2512             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2513             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2514             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2515             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2516             seekNrList[nrOfSeekAds] = nr;
2517             zList[nrOfSeekAds] = 0;
2518             seekAdList[nrOfSeekAds++] = StrSave(buf);
2519             if(plot) PlotSeekAd(nrOfSeekAds-1);
2520         }
2521 }
2522
2523 void
2524 EraseSeekDot (int i)
2525 {
2526     int x = xList[i], y = yList[i], d=squareSize/4, k;
2527     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2528     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2529     // now replot every dot that overlapped
2530     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2531         int xx = xList[k], yy = yList[k];
2532         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2533             DrawSeekDot(xx, yy, colorList[k]);
2534     }
2535 }
2536
2537 void
2538 RemoveSeekAd (int nr)
2539 {
2540         int i;
2541         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2542             EraseSeekDot(i);
2543             if(seekAdList[i]) free(seekAdList[i]);
2544             seekAdList[i] = seekAdList[--nrOfSeekAds];
2545             seekNrList[i] = seekNrList[nrOfSeekAds];
2546             ratingList[i] = ratingList[nrOfSeekAds];
2547             colorList[i]  = colorList[nrOfSeekAds];
2548             tcList[i] = tcList[nrOfSeekAds];
2549             xList[i]  = xList[nrOfSeekAds];
2550             yList[i]  = yList[nrOfSeekAds];
2551             zList[i]  = zList[nrOfSeekAds];
2552             seekAdList[nrOfSeekAds] = NULL;
2553             break;
2554         }
2555 }
2556
2557 Boolean
2558 MatchSoughtLine (char *line)
2559 {
2560     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2561     int nr, base, inc, u=0; char dummy;
2562
2563     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2564        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2565        (u=1) &&
2566        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2567         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2568         // match: compact and save the line
2569         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2570         return TRUE;
2571     }
2572     return FALSE;
2573 }
2574
2575 int
2576 DrawSeekGraph ()
2577 {
2578     int i;
2579     if(!seekGraphUp) return FALSE;
2580     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2581     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2582
2583     DrawSeekBackground(0, 0, w, h);
2584     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2585     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2586     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2587         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2588         yy = h-1-yy;
2589         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2590         if(i%500 == 0) {
2591             char buf[MSG_SIZ];
2592             snprintf(buf, MSG_SIZ, "%d", i);
2593             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2594         }
2595     }
2596     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2597     for(i=1; i<100; i+=(i<10?1:5)) {
2598         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2599         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2600         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2601             char buf[MSG_SIZ];
2602             snprintf(buf, MSG_SIZ, "%d", i);
2603             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2604         }
2605     }
2606     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2607     return TRUE;
2608 }
2609
2610 int
2611 SeekGraphClick (ClickType click, int x, int y, int moving)
2612 {
2613     static int lastDown = 0, displayed = 0, lastSecond;
2614     if(y < 0) return FALSE;
2615     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2616         if(click == Release || moving) return FALSE;
2617         nrOfSeekAds = 0;
2618         soughtPending = TRUE;
2619         SendToICS(ics_prefix);
2620         SendToICS("sought\n"); // should this be "sought all"?
2621     } else { // issue challenge based on clicked ad
2622         int dist = 10000; int i, closest = 0, second = 0;
2623         for(i=0; i<nrOfSeekAds; i++) {
2624             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2625             if(d < dist) { dist = d; closest = i; }
2626             second += (d - zList[i] < 120); // count in-range ads
2627             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2628         }
2629         if(dist < 120) {
2630             char buf[MSG_SIZ];
2631             second = (second > 1);
2632             if(displayed != closest || second != lastSecond) {
2633                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2634                 lastSecond = second; displayed = closest;
2635             }
2636             if(click == Press) {
2637                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2638                 lastDown = closest;
2639                 return TRUE;
2640             } // on press 'hit', only show info
2641             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2642             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2643             SendToICS(ics_prefix);
2644             SendToICS(buf);
2645             return TRUE; // let incoming board of started game pop down the graph
2646         } else if(click == Release) { // release 'miss' is ignored
2647             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2648             if(moving == 2) { // right up-click
2649                 nrOfSeekAds = 0; // refresh graph
2650                 soughtPending = TRUE;
2651                 SendToICS(ics_prefix);
2652                 SendToICS("sought\n"); // should this be "sought all"?
2653             }
2654             return TRUE;
2655         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2656         // press miss or release hit 'pop down' seek graph
2657         seekGraphUp = FALSE;
2658         DrawPosition(TRUE, NULL);
2659     }
2660     return TRUE;
2661 }
2662
2663 void
2664 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2665 {
2666 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2667 #define STARTED_NONE 0
2668 #define STARTED_MOVES 1
2669 #define STARTED_BOARD 2
2670 #define STARTED_OBSERVE 3
2671 #define STARTED_HOLDINGS 4
2672 #define STARTED_CHATTER 5
2673 #define STARTED_COMMENT 6
2674 #define STARTED_MOVES_NOHIDE 7
2675
2676     static int started = STARTED_NONE;
2677     static char parse[20000];
2678     static int parse_pos = 0;
2679     static char buf[BUF_SIZE + 1];
2680     static int firstTime = TRUE, intfSet = FALSE;
2681     static ColorClass prevColor = ColorNormal;
2682     static int savingComment = FALSE;
2683     static int cmatch = 0; // continuation sequence match
2684     char *bp;
2685     char str[MSG_SIZ];
2686     int i, oldi;
2687     int buf_len;
2688     int next_out;
2689     int tkind;
2690     int backup;    /* [DM] For zippy color lines */
2691     char *p;
2692     char talker[MSG_SIZ]; // [HGM] chat
2693     int channel;
2694
2695     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2696
2697     if (appData.debugMode) {
2698       if (!error) {
2699         fprintf(debugFP, "<ICS: ");
2700         show_bytes(debugFP, data, count);
2701         fprintf(debugFP, "\n");
2702       }
2703     }
2704
2705     if (appData.debugMode) { int f = forwardMostMove;
2706         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2707                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2708                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2709     }
2710     if (count > 0) {
2711         /* If last read ended with a partial line that we couldn't parse,
2712            prepend it to the new read and try again. */
2713         if (leftover_len > 0) {
2714             for (i=0; i<leftover_len; i++)
2715               buf[i] = buf[leftover_start + i];
2716         }
2717
2718     /* copy new characters into the buffer */
2719     bp = buf + leftover_len;
2720     buf_len=leftover_len;
2721     for (i=0; i<count; i++)
2722     {
2723         // ignore these
2724         if (data[i] == '\r')
2725             continue;
2726
2727         // join lines split by ICS?
2728         if (!appData.noJoin)
2729         {
2730             /*
2731                 Joining just consists of finding matches against the
2732                 continuation sequence, and discarding that sequence
2733                 if found instead of copying it.  So, until a match
2734                 fails, there's nothing to do since it might be the
2735                 complete sequence, and thus, something we don't want
2736                 copied.
2737             */
2738             if (data[i] == cont_seq[cmatch])
2739             {
2740                 cmatch++;
2741                 if (cmatch == strlen(cont_seq))
2742                 {
2743                     cmatch = 0; // complete match.  just reset the counter
2744
2745                     /*
2746                         it's possible for the ICS to not include the space
2747                         at the end of the last word, making our [correct]
2748                         join operation fuse two separate words.  the server
2749                         does this when the space occurs at the width setting.
2750                     */
2751                     if (!buf_len || buf[buf_len-1] != ' ')
2752                     {
2753                         *bp++ = ' ';
2754                         buf_len++;
2755                     }
2756                 }
2757                 continue;
2758             }
2759             else if (cmatch)
2760             {
2761                 /*
2762                     match failed, so we have to copy what matched before
2763                     falling through and copying this character.  In reality,
2764                     this will only ever be just the newline character, but
2765                     it doesn't hurt to be precise.
2766                 */
2767                 strncpy(bp, cont_seq, cmatch);
2768                 bp += cmatch;
2769                 buf_len += cmatch;
2770                 cmatch = 0;
2771             }
2772         }
2773
2774         // copy this char
2775         *bp++ = data[i];
2776         buf_len++;
2777     }
2778
2779         buf[buf_len] = NULLCHAR;
2780 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2781         next_out = 0;
2782         leftover_start = 0;
2783
2784         i = 0;
2785         while (i < buf_len) {
2786             /* Deal with part of the TELNET option negotiation
2787                protocol.  We refuse to do anything beyond the
2788                defaults, except that we allow the WILL ECHO option,
2789                which ICS uses to turn off password echoing when we are
2790                directly connected to it.  We reject this option
2791                if localLineEditing mode is on (always on in xboard)
2792                and we are talking to port 23, which might be a real
2793                telnet server that will try to keep WILL ECHO on permanently.
2794              */
2795             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2796                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2797                 unsigned char option;
2798                 oldi = i;
2799                 switch ((unsigned char) buf[++i]) {
2800                   case TN_WILL:
2801                     if (appData.debugMode)
2802                       fprintf(debugFP, "\n<WILL ");
2803                     switch (option = (unsigned char) buf[++i]) {
2804                       case TN_ECHO:
2805                         if (appData.debugMode)
2806                           fprintf(debugFP, "ECHO ");
2807                         /* Reply only if this is a change, according
2808                            to the protocol rules. */
2809                         if (remoteEchoOption) break;
2810                         if (appData.localLineEditing &&
2811                             atoi(appData.icsPort) == TN_PORT) {
2812                             TelnetRequest(TN_DONT, TN_ECHO);
2813                         } else {
2814                             EchoOff();
2815                             TelnetRequest(TN_DO, TN_ECHO);
2816                             remoteEchoOption = TRUE;
2817                         }
2818                         break;
2819                       default:
2820                         if (appData.debugMode)
2821                           fprintf(debugFP, "%d ", option);
2822                         /* Whatever this is, we don't want it. */
2823                         TelnetRequest(TN_DONT, option);
2824                         break;
2825                     }
2826                     break;
2827                   case TN_WONT:
2828                     if (appData.debugMode)
2829                       fprintf(debugFP, "\n<WONT ");
2830                     switch (option = (unsigned char) buf[++i]) {
2831                       case TN_ECHO:
2832                         if (appData.debugMode)
2833                           fprintf(debugFP, "ECHO ");
2834                         /* Reply only if this is a change, according
2835                            to the protocol rules. */
2836                         if (!remoteEchoOption) break;
2837                         EchoOn();
2838                         TelnetRequest(TN_DONT, TN_ECHO);
2839                         remoteEchoOption = FALSE;
2840                         break;
2841                       default:
2842                         if (appData.debugMode)
2843                           fprintf(debugFP, "%d ", (unsigned char) option);
2844                         /* Whatever this is, it must already be turned
2845                            off, because we never agree to turn on
2846                            anything non-default, so according to the
2847                            protocol rules, we don't reply. */
2848                         break;
2849                     }
2850                     break;
2851                   case TN_DO:
2852                     if (appData.debugMode)
2853                       fprintf(debugFP, "\n<DO ");
2854                     switch (option = (unsigned char) buf[++i]) {
2855                       default:
2856                         /* Whatever this is, we refuse to do it. */
2857                         if (appData.debugMode)
2858                           fprintf(debugFP, "%d ", option);
2859                         TelnetRequest(TN_WONT, option);
2860                         break;
2861                     }
2862                     break;
2863                   case TN_DONT:
2864                     if (appData.debugMode)
2865                       fprintf(debugFP, "\n<DONT ");
2866                     switch (option = (unsigned char) buf[++i]) {
2867                       default:
2868                         if (appData.debugMode)
2869                           fprintf(debugFP, "%d ", option);
2870                         /* Whatever this is, we are already not doing
2871                            it, because we never agree to do anything
2872                            non-default, so according to the protocol
2873                            rules, we don't reply. */
2874                         break;
2875                     }
2876                     break;
2877                   case TN_IAC:
2878                     if (appData.debugMode)
2879                       fprintf(debugFP, "\n<IAC ");
2880                     /* Doubled IAC; pass it through */
2881                     i--;
2882                     break;
2883                   default:
2884                     if (appData.debugMode)
2885                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2886                     /* Drop all other telnet commands on the floor */
2887                     break;
2888                 }
2889                 if (oldi > next_out)
2890                   SendToPlayer(&buf[next_out], oldi - next_out);
2891                 if (++i > next_out)
2892                   next_out = i;
2893                 continue;
2894             }
2895
2896             /* OK, this at least will *usually* work */
2897             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2898                 loggedOn = TRUE;
2899             }
2900
2901             if (loggedOn && !intfSet) {
2902                 if (ics_type == ICS_ICC) {
2903                   snprintf(str, MSG_SIZ,
2904                           "/set-quietly interface %s\n/set-quietly style 12\n",
2905                           programVersion);
2906                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2907                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2908                 } else if (ics_type == ICS_CHESSNET) {
2909                   snprintf(str, MSG_SIZ, "/style 12\n");
2910                 } else {
2911                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2912                   strcat(str, programVersion);
2913                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2914                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2915                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2916 #ifdef WIN32
2917                   strcat(str, "$iset nohighlight 1\n");
2918 #endif
2919                   strcat(str, "$iset lock 1\n$style 12\n");
2920                 }
2921                 SendToICS(str);
2922                 NotifyFrontendLogin();
2923                 intfSet = TRUE;
2924             }
2925
2926             if (started == STARTED_COMMENT) {
2927                 /* Accumulate characters in comment */
2928                 parse[parse_pos++] = buf[i];
2929                 if (buf[i] == '\n') {
2930                     parse[parse_pos] = NULLCHAR;
2931                     if(chattingPartner>=0) {
2932                         char mess[MSG_SIZ];
2933                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2934                         OutputChatMessage(chattingPartner, mess);
2935                         chattingPartner = -1;
2936                         next_out = i+1; // [HGM] suppress printing in ICS window
2937                     } else
2938                     if(!suppressKibitz) // [HGM] kibitz
2939                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2940                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2941                         int nrDigit = 0, nrAlph = 0, j;
2942                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2943                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2944                         parse[parse_pos] = NULLCHAR;
2945                         // try to be smart: if it does not look like search info, it should go to
2946                         // ICS interaction window after all, not to engine-output window.
2947                         for(j=0; j<parse_pos; j++) { // count letters and digits
2948                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2949                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2950                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2951                         }
2952                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2953                             int depth=0; float score;
2954                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2955                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2956                                 pvInfoList[forwardMostMove-1].depth = depth;
2957                                 pvInfoList[forwardMostMove-1].score = 100*score;
2958                             }
2959                             OutputKibitz(suppressKibitz, parse);
2960                         } else {
2961                             char tmp[MSG_SIZ];
2962                             if(gameMode == IcsObserving) // restore original ICS messages
2963                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2964                             else
2965                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2966                             SendToPlayer(tmp, strlen(tmp));
2967                         }
2968                         next_out = i+1; // [HGM] suppress printing in ICS window
2969                     }
2970                     started = STARTED_NONE;
2971                 } else {
2972                     /* Don't match patterns against characters in comment */
2973                     i++;
2974                     continue;
2975                 }
2976             }
2977             if (started == STARTED_CHATTER) {
2978                 if (buf[i] != '\n') {
2979                     /* Don't match patterns against characters in chatter */
2980                     i++;
2981                     continue;
2982                 }
2983                 started = STARTED_NONE;
2984                 if(suppressKibitz) next_out = i+1;
2985             }
2986
2987             /* Kludge to deal with rcmd protocol */
2988             if (firstTime && looking_at(buf, &i, "\001*")) {
2989                 DisplayFatalError(&buf[1], 0, 1);
2990                 continue;
2991             } else {
2992                 firstTime = FALSE;
2993             }
2994
2995             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2996                 ics_type = ICS_ICC;
2997                 ics_prefix = "/";
2998                 if (appData.debugMode)
2999                   fprintf(debugFP, "ics_type %d\n", ics_type);
3000                 continue;
3001             }
3002             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3003                 ics_type = ICS_FICS;
3004                 ics_prefix = "$";
3005                 if (appData.debugMode)
3006                   fprintf(debugFP, "ics_type %d\n", ics_type);
3007                 continue;
3008             }
3009             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3010                 ics_type = ICS_CHESSNET;
3011                 ics_prefix = "/";
3012                 if (appData.debugMode)
3013                   fprintf(debugFP, "ics_type %d\n", ics_type);
3014                 continue;
3015             }
3016
3017             if (!loggedOn &&
3018                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3019                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3020                  looking_at(buf, &i, "will be \"*\""))) {
3021               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3022               continue;
3023             }
3024
3025             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3026               char buf[MSG_SIZ];
3027               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3028               DisplayIcsInteractionTitle(buf);
3029               have_set_title = TRUE;
3030             }
3031
3032             /* skip finger notes */
3033             if (started == STARTED_NONE &&
3034                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3035                  (buf[i] == '1' && buf[i+1] == '0')) &&
3036                 buf[i+2] == ':' && buf[i+3] == ' ') {
3037               started = STARTED_CHATTER;
3038               i += 3;
3039               continue;
3040             }
3041
3042             oldi = i;
3043             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3044             if(appData.seekGraph) {
3045                 if(soughtPending && MatchSoughtLine(buf+i)) {
3046                     i = strstr(buf+i, "rated") - buf;
3047                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3048                     next_out = leftover_start = i;
3049                     started = STARTED_CHATTER;
3050                     suppressKibitz = TRUE;
3051                     continue;
3052                 }
3053                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3054                         && looking_at(buf, &i, "* ads displayed")) {
3055                     soughtPending = FALSE;
3056                     seekGraphUp = TRUE;
3057                     DrawSeekGraph();
3058                     continue;
3059                 }
3060                 if(appData.autoRefresh) {
3061                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3062                         int s = (ics_type == ICS_ICC); // ICC format differs
3063                         if(seekGraphUp)
3064                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3065                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3066                         looking_at(buf, &i, "*% "); // eat prompt
3067                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3068                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3069                         next_out = i; // suppress
3070                         continue;
3071                     }
3072                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3073                         char *p = star_match[0];
3074                         while(*p) {
3075                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3076                             while(*p && *p++ != ' '); // next
3077                         }
3078                         looking_at(buf, &i, "*% "); // eat prompt
3079                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3080                         next_out = i;
3081                         continue;
3082                     }
3083                 }
3084             }
3085
3086             /* skip formula vars */
3087             if (started == STARTED_NONE &&
3088                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3089               started = STARTED_CHATTER;
3090               i += 3;
3091               continue;
3092             }
3093
3094             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3095             if (appData.autoKibitz && started == STARTED_NONE &&
3096                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3097                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3098                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3099                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3100                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3101                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3102                         suppressKibitz = TRUE;
3103                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3104                         next_out = i;
3105                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3106                                 && (gameMode == IcsPlayingWhite)) ||
3107                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3108                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3109                             started = STARTED_CHATTER; // own kibitz we simply discard
3110                         else {
3111                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3112                             parse_pos = 0; parse[0] = NULLCHAR;
3113                             savingComment = TRUE;
3114                             suppressKibitz = gameMode != IcsObserving ? 2 :
3115                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3116                         }
3117                         continue;
3118                 } else
3119                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3120                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3121                          && atoi(star_match[0])) {
3122                     // suppress the acknowledgements of our own autoKibitz
3123                     char *p;
3124                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3125                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3126                     SendToPlayer(star_match[0], strlen(star_match[0]));
3127                     if(looking_at(buf, &i, "*% ")) // eat prompt
3128                         suppressKibitz = FALSE;
3129                     next_out = i;
3130                     continue;
3131                 }
3132             } // [HGM] kibitz: end of patch
3133
3134             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3135
3136             // [HGM] chat: intercept tells by users for which we have an open chat window
3137             channel = -1;
3138             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3139                                            looking_at(buf, &i, "* whispers:") ||
3140                                            looking_at(buf, &i, "* kibitzes:") ||
3141                                            looking_at(buf, &i, "* shouts:") ||
3142                                            looking_at(buf, &i, "* c-shouts:") ||
3143                                            looking_at(buf, &i, "--> * ") ||
3144                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3145                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3146                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3147                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3148                 int p;
3149                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3150                 chattingPartner = -1;
3151
3152                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3153                 for(p=0; p<MAX_CHAT; p++) {
3154                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3155                     talker[0] = '['; strcat(talker, "] ");
3156                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3157                     chattingPartner = p; break;
3158                     }
3159                 } else
3160                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3161                 for(p=0; p<MAX_CHAT; p++) {
3162                     if(!strcmp("kibitzes", chatPartner[p])) {
3163                         talker[0] = '['; strcat(talker, "] ");
3164                         chattingPartner = p; break;
3165                     }
3166                 } else
3167                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3168                 for(p=0; p<MAX_CHAT; p++) {
3169                     if(!strcmp("whispers", chatPartner[p])) {
3170                         talker[0] = '['; strcat(talker, "] ");
3171                         chattingPartner = p; break;
3172                     }
3173                 } else
3174                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3175                   if(buf[i-8] == '-' && buf[i-3] == 't')
3176                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3177                     if(!strcmp("c-shouts", chatPartner[p])) {
3178                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3179                         chattingPartner = p; break;
3180                     }
3181                   }
3182                   if(chattingPartner < 0)
3183                   for(p=0; p<MAX_CHAT; p++) {
3184                     if(!strcmp("shouts", chatPartner[p])) {
3185                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3186                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3187                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3188                         chattingPartner = p; break;
3189                     }
3190                   }
3191                 }
3192                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3193                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3194                     talker[0] = 0; Colorize(ColorTell, FALSE);
3195                     chattingPartner = p; break;
3196                 }
3197                 if(chattingPartner<0) i = oldi; else {
3198                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3199                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3200                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3201                     started = STARTED_COMMENT;
3202                     parse_pos = 0; parse[0] = NULLCHAR;
3203                     savingComment = 3 + chattingPartner; // counts as TRUE
3204                     suppressKibitz = TRUE;
3205                     continue;
3206                 }
3207             } // [HGM] chat: end of patch
3208
3209           backup = i;
3210             if (appData.zippyTalk || appData.zippyPlay) {
3211                 /* [DM] Backup address for color zippy lines */
3212 #if ZIPPY
3213                if (loggedOn == TRUE)
3214                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3215                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3216 #endif
3217             } // [DM] 'else { ' deleted
3218                 if (
3219                     /* Regular tells and says */
3220                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3221                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3222                     looking_at(buf, &i, "* says: ") ||
3223                     /* Don't color "message" or "messages" output */
3224                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3225                     looking_at(buf, &i, "*. * at *:*: ") ||
3226                     looking_at(buf, &i, "--* (*:*): ") ||
3227                     /* Message notifications (same color as tells) */
3228                     looking_at(buf, &i, "* has left a message ") ||
3229                     looking_at(buf, &i, "* just sent you a message:\n") ||
3230                     /* Whispers and kibitzes */
3231                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3232                     looking_at(buf, &i, "* kibitzes: ") ||
3233                     /* Channel tells */
3234                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3235
3236                   if (tkind == 1 && strchr(star_match[0], ':')) {
3237                       /* Avoid "tells you:" spoofs in channels */
3238                      tkind = 3;
3239                   }
3240                   if (star_match[0][0] == NULLCHAR ||
3241                       strchr(star_match[0], ' ') ||
3242                       (tkind == 3 && strchr(star_match[1], ' '))) {
3243                     /* Reject bogus matches */
3244                     i = oldi;
3245                   } else {
3246                     if (appData.colorize) {
3247                       if (oldi > next_out) {
3248                         SendToPlayer(&buf[next_out], oldi - next_out);
3249                         next_out = oldi;
3250                       }
3251                       switch (tkind) {
3252                       case 1:
3253                         Colorize(ColorTell, FALSE);
3254                         curColor = ColorTell;
3255                         break;
3256                       case 2:
3257                         Colorize(ColorKibitz, FALSE);
3258                         curColor = ColorKibitz;
3259                         break;
3260                       case 3:
3261                         p = strrchr(star_match[1], '(');
3262                         if (p == NULL) {
3263                           p = star_match[1];
3264                         } else {
3265                           p++;
3266                         }
3267                         if (atoi(p) == 1) {
3268                           Colorize(ColorChannel1, FALSE);
3269                           curColor = ColorChannel1;
3270                         } else {
3271                           Colorize(ColorChannel, FALSE);
3272                           curColor = ColorChannel;
3273                         }
3274                         break;
3275                       case 5:
3276                         curColor = ColorNormal;
3277                         break;
3278                       }
3279                     }
3280                     if (started == STARTED_NONE && appData.autoComment &&
3281                         (gameMode == IcsObserving ||
3282                          gameMode == IcsPlayingWhite ||
3283                          gameMode == IcsPlayingBlack)) {
3284                       parse_pos = i - oldi;
3285                       memcpy(parse, &buf[oldi], parse_pos);
3286                       parse[parse_pos] = NULLCHAR;
3287                       started = STARTED_COMMENT;
3288                       savingComment = TRUE;
3289                     } else {
3290                       started = STARTED_CHATTER;
3291                       savingComment = FALSE;
3292                     }
3293                     loggedOn = TRUE;
3294                     continue;
3295                   }
3296                 }
3297
3298                 if (looking_at(buf, &i, "* s-shouts: ") ||
3299                     looking_at(buf, &i, "* c-shouts: ")) {
3300                     if (appData.colorize) {
3301                         if (oldi > next_out) {
3302                             SendToPlayer(&buf[next_out], oldi - next_out);
3303                             next_out = oldi;
3304                         }
3305                         Colorize(ColorSShout, FALSE);
3306                         curColor = ColorSShout;
3307                     }
3308                     loggedOn = TRUE;
3309                     started = STARTED_CHATTER;
3310                     continue;
3311                 }
3312
3313                 if (looking_at(buf, &i, "--->")) {
3314                     loggedOn = TRUE;
3315                     continue;
3316                 }
3317
3318                 if (looking_at(buf, &i, "* shouts: ") ||
3319                     looking_at(buf, &i, "--> ")) {
3320                     if (appData.colorize) {
3321                         if (oldi > next_out) {
3322                             SendToPlayer(&buf[next_out], oldi - next_out);
3323                             next_out = oldi;
3324                         }
3325                         Colorize(ColorShout, FALSE);
3326                         curColor = ColorShout;
3327                     }
3328                     loggedOn = TRUE;
3329                     started = STARTED_CHATTER;
3330                     continue;
3331                 }
3332
3333                 if (looking_at( buf, &i, "Challenge:")) {
3334                     if (appData.colorize) {
3335                         if (oldi > next_out) {
3336                             SendToPlayer(&buf[next_out], oldi - next_out);
3337                             next_out = oldi;
3338                         }
3339                         Colorize(ColorChallenge, FALSE);
3340                         curColor = ColorChallenge;
3341                     }
3342                     loggedOn = TRUE;
3343                     continue;
3344                 }
3345
3346                 if (looking_at(buf, &i, "* offers you") ||
3347                     looking_at(buf, &i, "* offers to be") ||
3348                     looking_at(buf, &i, "* would like to") ||
3349                     looking_at(buf, &i, "* requests to") ||
3350                     looking_at(buf, &i, "Your opponent offers") ||
3351                     looking_at(buf, &i, "Your opponent requests")) {
3352
3353                     if (appData.colorize) {
3354                         if (oldi > next_out) {
3355                             SendToPlayer(&buf[next_out], oldi - next_out);
3356                             next_out = oldi;
3357                         }
3358                         Colorize(ColorRequest, FALSE);
3359                         curColor = ColorRequest;
3360                     }
3361                     continue;
3362                 }
3363
3364                 if (looking_at(buf, &i, "* (*) seeking")) {
3365                     if (appData.colorize) {
3366                         if (oldi > next_out) {
3367                             SendToPlayer(&buf[next_out], oldi - next_out);
3368                             next_out = oldi;
3369                         }
3370                         Colorize(ColorSeek, FALSE);
3371                         curColor = ColorSeek;
3372                     }
3373                     continue;
3374             }
3375
3376           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3377
3378             if (looking_at(buf, &i, "\\   ")) {
3379                 if (prevColor != ColorNormal) {
3380                     if (oldi > next_out) {
3381                         SendToPlayer(&buf[next_out], oldi - next_out);
3382                         next_out = oldi;
3383                     }
3384                     Colorize(prevColor, TRUE);
3385                     curColor = prevColor;
3386                 }
3387                 if (savingComment) {
3388                     parse_pos = i - oldi;
3389                     memcpy(parse, &buf[oldi], parse_pos);
3390                     parse[parse_pos] = NULLCHAR;
3391                     started = STARTED_COMMENT;
3392                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3393                         chattingPartner = savingComment - 3; // kludge to remember the box
3394                 } else {
3395                     started = STARTED_CHATTER;
3396                 }
3397                 continue;
3398             }
3399
3400             if (looking_at(buf, &i, "Black Strength :") ||
3401                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3402                 looking_at(buf, &i, "<10>") ||
3403                 looking_at(buf, &i, "#@#")) {
3404                 /* Wrong board style */
3405                 loggedOn = TRUE;
3406                 SendToICS(ics_prefix);
3407                 SendToICS("set style 12\n");
3408                 SendToICS(ics_prefix);
3409                 SendToICS("refresh\n");
3410                 continue;
3411             }
3412
3413             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3414                 ICSInitScript();
3415                 have_sent_ICS_logon = 1;
3416                 continue;
3417             }
3418
3419             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3420                 (looking_at(buf, &i, "\n<12> ") ||
3421                  looking_at(buf, &i, "<12> "))) {
3422                 loggedOn = TRUE;
3423                 if (oldi > next_out) {
3424                     SendToPlayer(&buf[next_out], oldi - next_out);
3425                 }
3426                 next_out = i;
3427                 started = STARTED_BOARD;
3428                 parse_pos = 0;
3429                 continue;
3430             }
3431
3432             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3433                 looking_at(buf, &i, "<b1> ")) {
3434                 if (oldi > next_out) {
3435                     SendToPlayer(&buf[next_out], oldi - next_out);
3436                 }
3437                 next_out = i;
3438                 started = STARTED_HOLDINGS;
3439                 parse_pos = 0;
3440                 continue;
3441             }
3442
3443             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3444                 loggedOn = TRUE;
3445                 /* Header for a move list -- first line */
3446
3447                 switch (ics_getting_history) {
3448                   case H_FALSE:
3449                     switch (gameMode) {
3450                       case IcsIdle:
3451                       case BeginningOfGame:
3452                         /* User typed "moves" or "oldmoves" while we
3453                            were idle.  Pretend we asked for these
3454                            moves and soak them up so user can step
3455                            through them and/or save them.
3456                            */
3457                         Reset(FALSE, TRUE);
3458                         gameMode = IcsObserving;
3459                         ModeHighlight();
3460                         ics_gamenum = -1;
3461                         ics_getting_history = H_GOT_UNREQ_HEADER;
3462                         break;
3463                       case EditGame: /*?*/
3464                       case EditPosition: /*?*/
3465                         /* Should above feature work in these modes too? */
3466                         /* For now it doesn't */
3467                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3468                         break;
3469                       default:
3470                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3471                         break;
3472                     }
3473                     break;
3474                   case H_REQUESTED:
3475                     /* Is this the right one? */
3476                     if (gameInfo.white && gameInfo.black &&
3477                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3478                         strcmp(gameInfo.black, star_match[2]) == 0) {
3479                         /* All is well */
3480                         ics_getting_history = H_GOT_REQ_HEADER;
3481                     }
3482                     break;
3483                   case H_GOT_REQ_HEADER:
3484                   case H_GOT_UNREQ_HEADER:
3485                   case H_GOT_UNWANTED_HEADER:
3486                   case H_GETTING_MOVES:
3487                     /* Should not happen */
3488                     DisplayError(_("Error gathering move list: two headers"), 0);
3489                     ics_getting_history = H_FALSE;
3490                     break;
3491                 }
3492
3493                 /* Save player ratings into gameInfo if needed */
3494                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3495                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3496                     (gameInfo.whiteRating == -1 ||
3497                      gameInfo.blackRating == -1)) {
3498
3499                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3500                     gameInfo.blackRating = string_to_rating(star_match[3]);
3501                     if (appData.debugMode)
3502                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3503                               gameInfo.whiteRating, gameInfo.blackRating);
3504                 }
3505                 continue;
3506             }
3507
3508             if (looking_at(buf, &i,
3509               "* * match, initial time: * minute*, increment: * second")) {
3510                 /* Header for a move list -- second line */
3511                 /* Initial board will follow if this is a wild game */
3512                 if (gameInfo.event != NULL) free(gameInfo.event);
3513                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3514                 gameInfo.event = StrSave(str);
3515                 /* [HGM] we switched variant. Translate boards if needed. */
3516                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3517                 continue;
3518             }
3519
3520             if (looking_at(buf, &i, "Move  ")) {
3521                 /* Beginning of a move list */
3522                 switch (ics_getting_history) {
3523                   case H_FALSE:
3524                     /* Normally should not happen */
3525                     /* Maybe user hit reset while we were parsing */
3526                     break;
3527                   case H_REQUESTED:
3528                     /* Happens if we are ignoring a move list that is not
3529                      * the one we just requested.  Common if the user
3530                      * tries to observe two games without turning off
3531                      * getMoveList */
3532                     break;
3533                   case H_GETTING_MOVES:
3534                     /* Should not happen */
3535                     DisplayError(_("Error gathering move list: nested"), 0);
3536                     ics_getting_history = H_FALSE;
3537                     break;
3538                   case H_GOT_REQ_HEADER:
3539                     ics_getting_history = H_GETTING_MOVES;
3540                     started = STARTED_MOVES;
3541                     parse_pos = 0;
3542                     if (oldi > next_out) {
3543                         SendToPlayer(&buf[next_out], oldi - next_out);
3544                     }
3545                     break;
3546                   case H_GOT_UNREQ_HEADER:
3547                     ics_getting_history = H_GETTING_MOVES;
3548                     started = STARTED_MOVES_NOHIDE;
3549                     parse_pos = 0;
3550                     break;
3551                   case H_GOT_UNWANTED_HEADER:
3552                     ics_getting_history = H_FALSE;
3553                     break;
3554                 }
3555                 continue;
3556             }
3557
3558             if (looking_at(buf, &i, "% ") ||
3559                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3560                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3561                 if(soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3562                     soughtPending = FALSE;
3563                     seekGraphUp = TRUE;
3564                     DrawSeekGraph();
3565                 }
3566                 if(suppressKibitz) next_out = i;
3567                 savingComment = FALSE;
3568                 suppressKibitz = 0;
3569                 switch (started) {
3570                   case STARTED_MOVES:
3571                   case STARTED_MOVES_NOHIDE:
3572                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3573                     parse[parse_pos + i - oldi] = NULLCHAR;
3574                     ParseGameHistory(parse);
3575 #if ZIPPY
3576                     if (appData.zippyPlay && first.initDone) {
3577                         FeedMovesToProgram(&first, forwardMostMove);
3578                         if (gameMode == IcsPlayingWhite) {
3579                             if (WhiteOnMove(forwardMostMove)) {
3580                                 if (first.sendTime) {
3581                                   if (first.useColors) {
3582                                     SendToProgram("black\n", &first);
3583                                   }
3584                                   SendTimeRemaining(&first, TRUE);
3585                                 }
3586                                 if (first.useColors) {
3587                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3588                                 }
3589                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3590                                 first.maybeThinking = TRUE;
3591                             } else {
3592                                 if (first.usePlayother) {
3593                                   if (first.sendTime) {
3594                                     SendTimeRemaining(&first, TRUE);
3595                                   }
3596                                   SendToProgram("playother\n", &first);
3597                                   firstMove = FALSE;
3598                                 } else {
3599                                   firstMove = TRUE;
3600                                 }
3601                             }
3602                         } else if (gameMode == IcsPlayingBlack) {
3603                             if (!WhiteOnMove(forwardMostMove)) {
3604                                 if (first.sendTime) {
3605                                   if (first.useColors) {
3606                                     SendToProgram("white\n", &first);
3607                                   }
3608                                   SendTimeRemaining(&first, FALSE);
3609                                 }
3610                                 if (first.useColors) {
3611                                   SendToProgram("black\n", &first);
3612                                 }
3613                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3614                                 first.maybeThinking = TRUE;
3615                             } else {
3616                                 if (first.usePlayother) {
3617                                   if (first.sendTime) {
3618                                     SendTimeRemaining(&first, FALSE);
3619                                   }
3620                                   SendToProgram("playother\n", &first);
3621                                   firstMove = FALSE;
3622                                 } else {
3623                                   firstMove = TRUE;
3624                                 }
3625                             }
3626                         }
3627                     }
3628 #endif
3629                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3630                         /* Moves came from oldmoves or moves command
3631                            while we weren't doing anything else.
3632                            */
3633                         currentMove = forwardMostMove;
3634                         ClearHighlights();/*!!could figure this out*/
3635                         flipView = appData.flipView;
3636                         DrawPosition(TRUE, boards[currentMove]);
3637                         DisplayBothClocks();
3638                         snprintf(str, MSG_SIZ, "%s %s %s",
3639                                 gameInfo.white, _("vs."),  gameInfo.black);
3640                         DisplayTitle(str);
3641                         gameMode = IcsIdle;
3642                     } else {
3643                         /* Moves were history of an active game */
3644                         if (gameInfo.resultDetails != NULL) {
3645                             free(gameInfo.resultDetails);
3646                             gameInfo.resultDetails = NULL;
3647                         }
3648                     }
3649                     HistorySet(parseList, backwardMostMove,
3650                                forwardMostMove, currentMove-1);
3651                     DisplayMove(currentMove - 1);
3652                     if (started == STARTED_MOVES) next_out = i;
3653                     started = STARTED_NONE;
3654                     ics_getting_history = H_FALSE;
3655                     break;
3656
3657                   case STARTED_OBSERVE:
3658                     started = STARTED_NONE;
3659                     SendToICS(ics_prefix);
3660                     SendToICS("refresh\n");
3661                     break;
3662
3663                   default:
3664                     break;
3665                 }
3666                 if(bookHit) { // [HGM] book: simulate book reply
3667                     static char bookMove[MSG_SIZ]; // a bit generous?
3668
3669                     programStats.nodes = programStats.depth = programStats.time =
3670                     programStats.score = programStats.got_only_move = 0;
3671                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3672
3673                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3674                     strcat(bookMove, bookHit);
3675                     HandleMachineMove(bookMove, &first);
3676                 }
3677                 continue;
3678             }
3679
3680             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3681                  started == STARTED_HOLDINGS ||
3682                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3683                 /* Accumulate characters in move list or board */
3684                 parse[parse_pos++] = buf[i];
3685             }
3686
3687             /* Start of game messages.  Mostly we detect start of game
3688                when the first board image arrives.  On some versions
3689                of the ICS, though, we need to do a "refresh" after starting
3690                to observe in order to get the current board right away. */
3691             if (looking_at(buf, &i, "Adding game * to observation list")) {
3692                 started = STARTED_OBSERVE;
3693                 continue;
3694             }
3695
3696             /* Handle auto-observe */
3697             if (appData.autoObserve &&
3698                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3699                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3700                 char *player;
3701                 /* Choose the player that was highlighted, if any. */
3702                 if (star_match[0][0] == '\033' ||
3703                     star_match[1][0] != '\033') {
3704                     player = star_match[0];
3705                 } else {
3706                     player = star_match[2];
3707                 }
3708                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3709                         ics_prefix, StripHighlightAndTitle(player));
3710                 SendToICS(str);
3711
3712                 /* Save ratings from notify string */
3713                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3714                 player1Rating = string_to_rating(star_match[1]);
3715                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3716                 player2Rating = string_to_rating(star_match[3]);
3717
3718                 if (appData.debugMode)
3719                   fprintf(debugFP,
3720                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3721                           player1Name, player1Rating,
3722                           player2Name, player2Rating);
3723
3724                 continue;
3725             }
3726
3727             /* Deal with automatic examine mode after a game,
3728                and with IcsObserving -> IcsExamining transition */
3729             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3730                 looking_at(buf, &i, "has made you an examiner of game *")) {
3731
3732                 int gamenum = atoi(star_match[0]);
3733                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3734                     gamenum == ics_gamenum) {
3735                     /* We were already playing or observing this game;
3736                        no need to refetch history */
3737                     gameMode = IcsExamining;
3738                     if (pausing) {
3739                         pauseExamForwardMostMove = forwardMostMove;
3740                     } else if (currentMove < forwardMostMove) {
3741                         ForwardInner(forwardMostMove);
3742                     }
3743                 } else {
3744                     /* I don't think this case really can happen */
3745                     SendToICS(ics_prefix);
3746                     SendToICS("refresh\n");
3747                 }
3748                 continue;
3749             }
3750
3751             /* Error messages */
3752 //          if (ics_user_moved) {
3753             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3754                 if (looking_at(buf, &i, "Illegal move") ||
3755                     looking_at(buf, &i, "Not a legal move") ||
3756                     looking_at(buf, &i, "Your king is in check") ||
3757                     looking_at(buf, &i, "It isn't your turn") ||
3758                     looking_at(buf, &i, "It is not your move")) {
3759                     /* Illegal move */
3760                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3761                         currentMove = forwardMostMove-1;
3762                         DisplayMove(currentMove - 1); /* before DMError */
3763                         DrawPosition(FALSE, boards[currentMove]);
3764                         SwitchClocks(forwardMostMove-1); // [HGM] race
3765                         DisplayBothClocks();
3766                     }
3767                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3768                     ics_user_moved = 0;
3769                     continue;
3770                 }
3771             }
3772
3773             if (looking_at(buf, &i, "still have time") ||
3774                 looking_at(buf, &i, "not out of time") ||
3775                 looking_at(buf, &i, "either player is out of time") ||
3776                 looking_at(buf, &i, "has timeseal; checking")) {
3777                 /* We must have called his flag a little too soon */
3778                 whiteFlag = blackFlag = FALSE;
3779                 continue;
3780             }
3781
3782             if (looking_at(buf, &i, "added * seconds to") ||
3783                 looking_at(buf, &i, "seconds were added to")) {
3784                 /* Update the clocks */
3785                 SendToICS(ics_prefix);
3786                 SendToICS("refresh\n");
3787                 continue;
3788             }
3789
3790             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3791                 ics_clock_paused = TRUE;
3792                 StopClocks();
3793                 continue;
3794             }
3795
3796             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3797                 ics_clock_paused = FALSE;
3798                 StartClocks();
3799                 continue;
3800             }
3801
3802             /* Grab player ratings from the Creating: message.
3803                Note we have to check for the special case when
3804                the ICS inserts things like [white] or [black]. */
3805             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3806                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3807                 /* star_matches:
3808                    0    player 1 name (not necessarily white)
3809                    1    player 1 rating
3810                    2    empty, white, or black (IGNORED)
3811                    3    player 2 name (not necessarily black)
3812                    4    player 2 rating
3813
3814                    The names/ratings are sorted out when the game
3815                    actually starts (below).
3816                 */
3817                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3818                 player1Rating = string_to_rating(star_match[1]);
3819                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3820                 player2Rating = string_to_rating(star_match[4]);
3821
3822                 if (appData.debugMode)
3823                   fprintf(debugFP,
3824                           "Ratings from 'Creating:' %s %d, %s %d\n",
3825                           player1Name, player1Rating,
3826                           player2Name, player2Rating);
3827
3828                 continue;
3829             }
3830
3831             /* Improved generic start/end-of-game messages */
3832             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3833                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3834                 /* If tkind == 0: */
3835                 /* star_match[0] is the game number */
3836                 /*           [1] is the white player's name */
3837                 /*           [2] is the black player's name */
3838                 /* For end-of-game: */
3839                 /*           [3] is the reason for the game end */
3840                 /*           [4] is a PGN end game-token, preceded by " " */
3841                 /* For start-of-game: */
3842                 /*           [3] begins with "Creating" or "Continuing" */
3843                 /*           [4] is " *" or empty (don't care). */
3844                 int gamenum = atoi(star_match[0]);
3845                 char *whitename, *blackname, *why, *endtoken;
3846                 ChessMove endtype = EndOfFile;
3847
3848                 if (tkind == 0) {
3849                   whitename = star_match[1];
3850                   blackname = star_match[2];
3851                   why = star_match[3];
3852                   endtoken = star_match[4];
3853                 } else {
3854                   whitename = star_match[1];
3855                   blackname = star_match[3];
3856                   why = star_match[5];
3857                   endtoken = star_match[6];
3858                 }
3859
3860                 /* Game start messages */
3861                 if (strncmp(why, "Creating ", 9) == 0 ||
3862                     strncmp(why, "Continuing ", 11) == 0) {
3863                     gs_gamenum = gamenum;
3864                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3865                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3866                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3867 #if ZIPPY
3868                     if (appData.zippyPlay) {
3869                         ZippyGameStart(whitename, blackname);
3870                     }
3871 #endif /*ZIPPY*/
3872                     partnerBoardValid = FALSE; // [HGM] bughouse
3873                     continue;
3874                 }
3875
3876                 /* Game end messages */
3877                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3878                     ics_gamenum != gamenum) {
3879                     continue;
3880                 }
3881                 while (endtoken[0] == ' ') endtoken++;
3882                 switch (endtoken[0]) {
3883                   case '*':
3884                   default:
3885                     endtype = GameUnfinished;
3886                     break;
3887                   case '0':
3888                     endtype = BlackWins;
3889                     break;
3890                   case '1':
3891                     if (endtoken[1] == '/')
3892                       endtype = GameIsDrawn;
3893                     else
3894                       endtype = WhiteWins;
3895                     break;
3896                 }
3897                 GameEnds(endtype, why, GE_ICS);
3898 #if ZIPPY
3899                 if (appData.zippyPlay && first.initDone) {
3900                     ZippyGameEnd(endtype, why);
3901                     if (first.pr == NoProc) {
3902                       /* Start the next process early so that we'll
3903                          be ready for the next challenge */
3904                       StartChessProgram(&first);
3905                     }
3906                     /* Send "new" early, in case this command takes
3907                        a long time to finish, so that we'll be ready
3908                        for the next challenge. */
3909                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3910                     Reset(TRUE, TRUE);
3911                 }
3912 #endif /*ZIPPY*/
3913                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3914                 continue;
3915             }
3916
3917             if (looking_at(buf, &i, "Removing game * from observation") ||
3918                 looking_at(buf, &i, "no longer observing game *") ||
3919                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3920                 if (gameMode == IcsObserving &&
3921                     atoi(star_match[0]) == ics_gamenum)
3922                   {
3923                       /* icsEngineAnalyze */
3924                       if (appData.icsEngineAnalyze) {
3925                             ExitAnalyzeMode();
3926                             ModeHighlight();
3927                       }
3928                       StopClocks();
3929                       gameMode = IcsIdle;
3930                       ics_gamenum = -1;
3931                       ics_user_moved = FALSE;
3932                   }
3933                 continue;
3934             }
3935
3936             if (looking_at(buf, &i, "no longer examining game *")) {
3937                 if (gameMode == IcsExamining &&
3938                     atoi(star_match[0]) == ics_gamenum)
3939                   {
3940                       gameMode = IcsIdle;
3941                       ics_gamenum = -1;
3942                       ics_user_moved = FALSE;
3943                   }
3944                 continue;
3945             }
3946
3947             /* Advance leftover_start past any newlines we find,
3948                so only partial lines can get reparsed */
3949             if (looking_at(buf, &i, "\n")) {
3950                 prevColor = curColor;
3951                 if (curColor != ColorNormal) {
3952                     if (oldi > next_out) {
3953                         SendToPlayer(&buf[next_out], oldi - next_out);
3954                         next_out = oldi;
3955                     }
3956                     Colorize(ColorNormal, FALSE);
3957                     curColor = ColorNormal;
3958                 }
3959                 if (started == STARTED_BOARD) {
3960                     started = STARTED_NONE;
3961                     parse[parse_pos] = NULLCHAR;
3962                     ParseBoard12(parse);
3963                     ics_user_moved = 0;
3964
3965                     /* Send premove here */
3966                     if (appData.premove) {
3967                       char str[MSG_SIZ];
3968                       if (currentMove == 0 &&
3969                           gameMode == IcsPlayingWhite &&
3970                           appData.premoveWhite) {
3971                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3972                         if (appData.debugMode)
3973                           fprintf(debugFP, "Sending premove:\n");
3974                         SendToICS(str);
3975                       } else if (currentMove == 1 &&
3976                                  gameMode == IcsPlayingBlack &&
3977                                  appData.premoveBlack) {
3978                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3979                         if (appData.debugMode)
3980                           fprintf(debugFP, "Sending premove:\n");
3981                         SendToICS(str);
3982                       } else if (gotPremove) {
3983                         gotPremove = 0;
3984                         ClearPremoveHighlights();
3985                         if (appData.debugMode)
3986                           fprintf(debugFP, "Sending premove:\n");
3987                           UserMoveEvent(premoveFromX, premoveFromY,
3988                                         premoveToX, premoveToY,
3989                                         premovePromoChar);
3990                       }
3991                     }
3992
3993                     /* Usually suppress following prompt */
3994                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3995                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3996                         if (looking_at(buf, &i, "*% ")) {
3997                             savingComment = FALSE;
3998                             suppressKibitz = 0;
3999                         }
4000                     }
4001                     next_out = i;
4002                 } else if (started == STARTED_HOLDINGS) {
4003                     int gamenum;
4004                     char new_piece[MSG_SIZ];
4005                     started = STARTED_NONE;
4006                     parse[parse_pos] = NULLCHAR;
4007                     if (appData.debugMode)
4008                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4009                                                         parse, currentMove);
4010                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4011                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4012                         if (gameInfo.variant == VariantNormal) {
4013                           /* [HGM] We seem to switch variant during a game!
4014                            * Presumably no holdings were displayed, so we have
4015                            * to move the position two files to the right to
4016                            * create room for them!
4017                            */
4018                           VariantClass newVariant;
4019                           switch(gameInfo.boardWidth) { // base guess on board width
4020                                 case 9:  newVariant = VariantShogi; break;
4021                                 case 10: newVariant = VariantGreat; break;
4022                                 default: newVariant = VariantCrazyhouse; break;
4023                           }
4024                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4025                           /* Get a move list just to see the header, which
4026                              will tell us whether this is really bug or zh */
4027                           if (ics_getting_history == H_FALSE) {
4028                             ics_getting_history = H_REQUESTED;
4029                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4030                             SendToICS(str);
4031                           }
4032                         }
4033                         new_piece[0] = NULLCHAR;
4034                         sscanf(parse, "game %d white [%s black [%s <- %s",
4035                                &gamenum, white_holding, black_holding,
4036                                new_piece);
4037                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4038                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4039                         /* [HGM] copy holdings to board holdings area */
4040                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4041                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4042                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4043 #if ZIPPY
4044                         if (appData.zippyPlay && first.initDone) {
4045                             ZippyHoldings(white_holding, black_holding,
4046                                           new_piece);
4047                         }
4048 #endif /*ZIPPY*/
4049                         if (tinyLayout || smallLayout) {
4050                             char wh[16], bh[16];
4051                             PackHolding(wh, white_holding);
4052                             PackHolding(bh, black_holding);
4053                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4054                                     gameInfo.white, gameInfo.black);
4055                         } else {
4056                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4057                                     gameInfo.white, white_holding, _("vs."),
4058                                     gameInfo.black, black_holding);
4059                         }
4060                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4061                         DrawPosition(FALSE, boards[currentMove]);
4062                         DisplayTitle(str);
4063                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4064                         sscanf(parse, "game %d white [%s black [%s <- %s",
4065                                &gamenum, white_holding, black_holding,
4066                                new_piece);
4067                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4068                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4069                         /* [HGM] copy holdings to partner-board holdings area */
4070                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4071                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4072                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4073                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4074                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4075                       }
4076                     }
4077                     /* Suppress following prompt */
4078                     if (looking_at(buf, &i, "*% ")) {
4079                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4080                         savingComment = FALSE;
4081                         suppressKibitz = 0;
4082                     }
4083                     next_out = i;
4084                 }
4085                 continue;
4086             }
4087
4088             i++;                /* skip unparsed character and loop back */
4089         }
4090
4091         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4092 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4093 //          SendToPlayer(&buf[next_out], i - next_out);
4094             started != STARTED_HOLDINGS && leftover_start > next_out) {
4095             SendToPlayer(&buf[next_out], leftover_start - next_out);
4096             next_out = i;
4097         }
4098
4099         leftover_len = buf_len - leftover_start;
4100         /* if buffer ends with something we couldn't parse,
4101            reparse it after appending the next read */
4102
4103     } else if (count == 0) {
4104         RemoveInputSource(isr);
4105         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4106     } else {
4107         DisplayFatalError(_("Error reading from ICS"), error, 1);
4108     }
4109 }
4110
4111
4112 /* Board style 12 looks like this:
4113
4114    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4115
4116  * The "<12> " is stripped before it gets to this routine.  The two
4117  * trailing 0's (flip state and clock ticking) are later addition, and
4118  * some chess servers may not have them, or may have only the first.
4119  * Additional trailing fields may be added in the future.
4120  */
4121
4122 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4123
4124 #define RELATION_OBSERVING_PLAYED    0
4125 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4126 #define RELATION_PLAYING_MYMOVE      1
4127 #define RELATION_PLAYING_NOTMYMOVE  -1
4128 #define RELATION_EXAMINING           2
4129 #define RELATION_ISOLATED_BOARD     -3
4130 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4131
4132 void
4133 ParseBoard12 (char *string)
4134 {
4135     GameMode newGameMode;
4136     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4137     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4138     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4139     char to_play, board_chars[200];
4140     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4141     char black[32], white[32];
4142     Board board;
4143     int prevMove = currentMove;
4144     int ticking = 2;
4145     ChessMove moveType;
4146     int fromX, fromY, toX, toY;
4147     char promoChar;
4148     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4149     char *bookHit = NULL; // [HGM] book
4150     Boolean weird = FALSE, reqFlag = FALSE;
4151
4152     fromX = fromY = toX = toY = -1;
4153
4154     newGame = FALSE;
4155
4156     if (appData.debugMode)
4157       fprintf(debugFP, _("Parsing board: %s\n"), string);
4158
4159     move_str[0] = NULLCHAR;
4160     elapsed_time[0] = NULLCHAR;
4161     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4162         int  i = 0, j;
4163         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4164             if(string[i] == ' ') { ranks++; files = 0; }
4165             else files++;
4166             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4167             i++;
4168         }
4169         for(j = 0; j <i; j++) board_chars[j] = string[j];
4170         board_chars[i] = '\0';
4171         string += i + 1;
4172     }
4173     n = sscanf(string, PATTERN, &to_play, &double_push,
4174                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4175                &gamenum, white, black, &relation, &basetime, &increment,
4176                &white_stren, &black_stren, &white_time, &black_time,
4177                &moveNum, str, elapsed_time, move_str, &ics_flip,
4178                &ticking);
4179
4180     if (n < 21) {
4181         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4182         DisplayError(str, 0);
4183         return;
4184     }
4185
4186     /* Convert the move number to internal form */
4187     moveNum = (moveNum - 1) * 2;
4188     if (to_play == 'B') moveNum++;
4189     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4190       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4191                         0, 1);
4192       return;
4193     }
4194
4195     switch (relation) {
4196       case RELATION_OBSERVING_PLAYED:
4197       case RELATION_OBSERVING_STATIC:
4198         if (gamenum == -1) {
4199             /* Old ICC buglet */
4200             relation = RELATION_OBSERVING_STATIC;
4201         }
4202         newGameMode = IcsObserving;
4203         break;
4204       case RELATION_PLAYING_MYMOVE:
4205       case RELATION_PLAYING_NOTMYMOVE:
4206         newGameMode =
4207           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4208             IcsPlayingWhite : IcsPlayingBlack;
4209         break;
4210       case RELATION_EXAMINING:
4211         newGameMode = IcsExamining;
4212         break;
4213       case RELATION_ISOLATED_BOARD:
4214       default:
4215         /* Just display this board.  If user was doing something else,
4216            we will forget about it until the next board comes. */
4217         newGameMode = IcsIdle;
4218         break;
4219       case RELATION_STARTING_POSITION:
4220         newGameMode = gameMode;
4221         break;
4222     }
4223
4224     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4225          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4226       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4227       char *toSqr;
4228       for (k = 0; k < ranks; k++) {
4229         for (j = 0; j < files; j++)
4230           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4231         if(gameInfo.holdingsWidth > 1) {
4232              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4233              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4234         }
4235       }
4236       CopyBoard(partnerBoard, board);
4237       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4238         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4239         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4240       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4241       if(toSqr = strchr(str, '-')) {
4242         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4243         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4244       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4245       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4246       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4247       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4248       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4249       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4250                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4251       DisplayMessage(partnerStatus, "");
4252         partnerBoardValid = TRUE;
4253       return;
4254     }
4255
4256     /* Modify behavior for initial board display on move listing
4257        of wild games.
4258        */
4259     switch (ics_getting_history) {
4260       case H_FALSE:
4261       case H_REQUESTED:
4262         break;
4263       case H_GOT_REQ_HEADER:
4264       case H_GOT_UNREQ_HEADER:
4265         /* This is the initial position of the current game */
4266         gamenum = ics_gamenum;
4267         moveNum = 0;            /* old ICS bug workaround */
4268         if (to_play == 'B') {
4269           startedFromSetupPosition = TRUE;
4270           blackPlaysFirst = TRUE;
4271           moveNum = 1;
4272           if (forwardMostMove == 0) forwardMostMove = 1;
4273           if (backwardMostMove == 0) backwardMostMove = 1;
4274           if (currentMove == 0) currentMove = 1;
4275         }
4276         newGameMode = gameMode;
4277         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4278         break;
4279       case H_GOT_UNWANTED_HEADER:
4280         /* This is an initial board that we don't want */
4281         return;
4282       case H_GETTING_MOVES:
4283         /* Should not happen */
4284         DisplayError(_("Error gathering move list: extra board"), 0);
4285         ics_getting_history = H_FALSE;
4286         return;
4287     }
4288
4289    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4290                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4291      /* [HGM] We seem to have switched variant unexpectedly
4292       * Try to guess new variant from board size
4293       */
4294           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4295           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4296           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4297           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4298           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4299           if(!weird) newVariant = VariantNormal;
4300           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4301           /* Get a move list just to see the header, which
4302              will tell us whether this is really bug or zh */
4303           if (ics_getting_history == H_FALSE) {
4304             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4305             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4306             SendToICS(str);
4307           }
4308     }
4309
4310     /* Take action if this is the first board of a new game, or of a
4311        different game than is currently being displayed.  */
4312     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4313         relation == RELATION_ISOLATED_BOARD) {
4314
4315         /* Forget the old game and get the history (if any) of the new one */
4316         if (gameMode != BeginningOfGame) {
4317           Reset(TRUE, TRUE);
4318         }
4319         newGame = TRUE;
4320         if (appData.autoRaiseBoard) BoardToTop();
4321         prevMove = -3;
4322         if (gamenum == -1) {
4323             newGameMode = IcsIdle;
4324         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4325                    appData.getMoveList && !reqFlag) {
4326             /* Need to get game history */
4327             ics_getting_history = H_REQUESTED;
4328             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4329             SendToICS(str);
4330         }
4331
4332         /* Initially flip the board to have black on the bottom if playing
4333            black or if the ICS flip flag is set, but let the user change
4334            it with the Flip View button. */
4335         flipView = appData.autoFlipView ?
4336           (newGameMode == IcsPlayingBlack) || ics_flip :
4337           appData.flipView;
4338
4339         /* Done with values from previous mode; copy in new ones */
4340         gameMode = newGameMode;
4341         ModeHighlight();
4342         ics_gamenum = gamenum;
4343         if (gamenum == gs_gamenum) {
4344             int klen = strlen(gs_kind);
4345             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4346             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4347             gameInfo.event = StrSave(str);
4348         } else {
4349             gameInfo.event = StrSave("ICS game");
4350         }
4351         gameInfo.site = StrSave(appData.icsHost);
4352         gameInfo.date = PGNDate();
4353         gameInfo.round = StrSave("-");
4354         gameInfo.white = StrSave(white);
4355         gameInfo.black = StrSave(black);
4356         timeControl = basetime * 60 * 1000;
4357         timeControl_2 = 0;
4358         timeIncrement = increment * 1000;
4359         movesPerSession = 0;
4360         gameInfo.timeControl = TimeControlTagValue();
4361         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4362   if (appData.debugMode) {
4363     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4364     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4365     setbuf(debugFP, NULL);
4366   }
4367
4368         gameInfo.outOfBook = NULL;
4369
4370         /* Do we have the ratings? */
4371         if (strcmp(player1Name, white) == 0 &&
4372             strcmp(player2Name, black) == 0) {
4373             if (appData.debugMode)
4374               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4375                       player1Rating, player2Rating);
4376             gameInfo.whiteRating = player1Rating;
4377             gameInfo.blackRating = player2Rating;
4378         } else if (strcmp(player2Name, white) == 0 &&
4379                    strcmp(player1Name, black) == 0) {
4380             if (appData.debugMode)
4381               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4382                       player2Rating, player1Rating);
4383             gameInfo.whiteRating = player2Rating;
4384             gameInfo.blackRating = player1Rating;
4385         }
4386         player1Name[0] = player2Name[0] = NULLCHAR;
4387
4388         /* Silence shouts if requested */
4389         if (appData.quietPlay &&
4390             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4391             SendToICS(ics_prefix);
4392             SendToICS("set shout 0\n");
4393         }
4394     }
4395
4396     /* Deal with midgame name changes */
4397     if (!newGame) {
4398         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4399             if (gameInfo.white) free(gameInfo.white);
4400             gameInfo.white = StrSave(white);
4401         }
4402         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4403             if (gameInfo.black) free(gameInfo.black);
4404             gameInfo.black = StrSave(black);
4405         }
4406     }
4407
4408     /* Throw away game result if anything actually changes in examine mode */
4409     if (gameMode == IcsExamining && !newGame) {
4410         gameInfo.result = GameUnfinished;
4411         if (gameInfo.resultDetails != NULL) {
4412             free(gameInfo.resultDetails);
4413             gameInfo.resultDetails = NULL;
4414         }
4415     }
4416
4417     /* In pausing && IcsExamining mode, we ignore boards coming
4418        in if they are in a different variation than we are. */
4419     if (pauseExamInvalid) return;
4420     if (pausing && gameMode == IcsExamining) {
4421         if (moveNum <= pauseExamForwardMostMove) {
4422             pauseExamInvalid = TRUE;
4423             forwardMostMove = pauseExamForwardMostMove;
4424             return;
4425         }
4426     }
4427
4428   if (appData.debugMode) {
4429     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4430   }
4431     /* Parse the board */
4432     for (k = 0; k < ranks; k++) {
4433       for (j = 0; j < files; j++)
4434         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4435       if(gameInfo.holdingsWidth > 1) {
4436            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4437            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4438       }
4439     }
4440     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4441       board[5][BOARD_RGHT+1] = WhiteAngel;
4442       board[6][BOARD_RGHT+1] = WhiteMarshall;
4443       board[1][0] = BlackMarshall;
4444       board[2][0] = BlackAngel;
4445       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4446     }
4447     CopyBoard(boards[moveNum], board);
4448     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4449     if (moveNum == 0) {
4450         startedFromSetupPosition =
4451           !CompareBoards(board, initialPosition);
4452         if(startedFromSetupPosition)
4453             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4454     }
4455
4456     /* [HGM] Set castling rights. Take the outermost Rooks,
4457        to make it also work for FRC opening positions. Note that board12
4458        is really defective for later FRC positions, as it has no way to
4459        indicate which Rook can castle if they are on the same side of King.
4460        For the initial position we grant rights to the outermost Rooks,
4461        and remember thos rights, and we then copy them on positions
4462        later in an FRC game. This means WB might not recognize castlings with
4463        Rooks that have moved back to their original position as illegal,
4464        but in ICS mode that is not its job anyway.
4465     */
4466     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4467     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4468
4469         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4470             if(board[0][i] == WhiteRook) j = i;
4471         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4472         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4473             if(board[0][i] == WhiteRook) j = i;
4474         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4475         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4476             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4477         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4478         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4479             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4480         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4481
4482         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4483         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4484         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4485             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4486         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4487             if(board[BOARD_HEIGHT-1][k] == bKing)
4488                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4489         if(gameInfo.variant == VariantTwoKings) {
4490             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4491             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4492             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4493         }
4494     } else { int r;
4495         r = boards[moveNum][CASTLING][0] = initialRights[0];
4496         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4497         r = boards[moveNum][CASTLING][1] = initialRights[1];
4498         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4499         r = boards[moveNum][CASTLING][3] = initialRights[3];
4500         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4501         r = boards[moveNum][CASTLING][4] = initialRights[4];
4502         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4503         /* wildcastle kludge: always assume King has rights */
4504         r = boards[moveNum][CASTLING][2] = initialRights[2];
4505         r = boards[moveNum][CASTLING][5] = initialRights[5];
4506     }
4507     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4508     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4509
4510
4511     if (ics_getting_history == H_GOT_REQ_HEADER ||
4512         ics_getting_history == H_GOT_UNREQ_HEADER) {
4513         /* This was an initial position from a move list, not
4514            the current position */
4515         return;
4516     }
4517
4518     /* Update currentMove and known move number limits */
4519     newMove = newGame || moveNum > forwardMostMove;
4520
4521     if (newGame) {
4522         forwardMostMove = backwardMostMove = currentMove = moveNum;
4523         if (gameMode == IcsExamining && moveNum == 0) {
4524           /* Workaround for ICS limitation: we are not told the wild
4525              type when starting to examine a game.  But if we ask for
4526              the move list, the move list header will tell us */
4527             ics_getting_history = H_REQUESTED;
4528             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4529             SendToICS(str);
4530         }
4531     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4532                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4533 #if ZIPPY
4534         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4535         /* [HGM] applied this also to an engine that is silently watching        */
4536         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4537             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4538             gameInfo.variant == currentlyInitializedVariant) {
4539           takeback = forwardMostMove - moveNum;
4540           for (i = 0; i < takeback; i++) {
4541             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4542             SendToProgram("undo\n", &first);
4543           }
4544         }
4545 #endif
4546
4547         forwardMostMove = moveNum;
4548         if (!pausing || currentMove > forwardMostMove)
4549           currentMove = forwardMostMove;
4550     } else {
4551         /* New part of history that is not contiguous with old part */
4552         if (pausing && gameMode == IcsExamining) {
4553             pauseExamInvalid = TRUE;
4554             forwardMostMove = pauseExamForwardMostMove;
4555             return;
4556         }
4557         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4558 #if ZIPPY
4559             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4560                 // [HGM] when we will receive the move list we now request, it will be
4561                 // fed to the engine from the first move on. So if the engine is not
4562                 // in the initial position now, bring it there.
4563                 InitChessProgram(&first, 0);
4564             }
4565 #endif
4566             ics_getting_history = H_REQUESTED;
4567             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4568             SendToICS(str);
4569         }
4570         forwardMostMove = backwardMostMove = currentMove = moveNum;
4571     }
4572
4573     /* Update the clocks */
4574     if (strchr(elapsed_time, '.')) {
4575       /* Time is in ms */
4576       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4577       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4578     } else {
4579       /* Time is in seconds */
4580       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4581       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4582     }
4583
4584
4585 #if ZIPPY
4586     if (appData.zippyPlay && newGame &&
4587         gameMode != IcsObserving && gameMode != IcsIdle &&
4588         gameMode != IcsExamining)
4589       ZippyFirstBoard(moveNum, basetime, increment);
4590 #endif
4591
4592     /* Put the move on the move list, first converting
4593        to canonical algebraic form. */
4594     if (moveNum > 0) {
4595   if (appData.debugMode) {
4596     if (appData.debugMode) { int f = forwardMostMove;
4597         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4598                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4599                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4600     }
4601     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4602     fprintf(debugFP, "moveNum = %d\n", moveNum);
4603     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4604     setbuf(debugFP, NULL);
4605   }
4606         if (moveNum <= backwardMostMove) {
4607             /* We don't know what the board looked like before
4608                this move.  Punt. */
4609           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4610             strcat(parseList[moveNum - 1], " ");
4611             strcat(parseList[moveNum - 1], elapsed_time);
4612             moveList[moveNum - 1][0] = NULLCHAR;
4613         } else if (strcmp(move_str, "none") == 0) {
4614             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4615             /* Again, we don't know what the board looked like;
4616                this is really the start of the game. */
4617             parseList[moveNum - 1][0] = NULLCHAR;
4618             moveList[moveNum - 1][0] = NULLCHAR;
4619             backwardMostMove = moveNum;
4620             startedFromSetupPosition = TRUE;
4621             fromX = fromY = toX = toY = -1;
4622         } else {
4623           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4624           //                 So we parse the long-algebraic move string in stead of the SAN move
4625           int valid; char buf[MSG_SIZ], *prom;
4626
4627           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4628                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4629           // str looks something like "Q/a1-a2"; kill the slash
4630           if(str[1] == '/')
4631             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4632           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4633           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4634                 strcat(buf, prom); // long move lacks promo specification!
4635           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4636                 if(appData.debugMode)
4637                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4638                 safeStrCpy(move_str, buf, MSG_SIZ);
4639           }
4640           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4641                                 &fromX, &fromY, &toX, &toY, &promoChar)
4642                || ParseOneMove(buf, moveNum - 1, &moveType,
4643                                 &fromX, &fromY, &toX, &toY, &promoChar);
4644           // end of long SAN patch
4645           if (valid) {
4646             (void) CoordsToAlgebraic(boards[moveNum - 1],
4647                                      PosFlags(moveNum - 1),
4648                                      fromY, fromX, toY, toX, promoChar,
4649                                      parseList[moveNum-1]);
4650             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4651               case MT_NONE:
4652               case MT_STALEMATE:
4653               default:
4654                 break;
4655               case MT_CHECK:
4656                 if(gameInfo.variant != VariantShogi)
4657                     strcat(parseList[moveNum - 1], "+");
4658                 break;
4659               case MT_CHECKMATE:
4660               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4661                 strcat(parseList[moveNum - 1], "#");
4662                 break;
4663             }
4664             strcat(parseList[moveNum - 1], " ");
4665             strcat(parseList[moveNum - 1], elapsed_time);
4666             /* currentMoveString is set as a side-effect of ParseOneMove */
4667             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4668             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4669             strcat(moveList[moveNum - 1], "\n");
4670
4671             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4672                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4673               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4674                 ChessSquare old, new = boards[moveNum][k][j];
4675                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4676                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4677                   if(old == new) continue;
4678                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4679                   else if(new == WhiteWazir || new == BlackWazir) {
4680                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4681                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4682                       else boards[moveNum][k][j] = old; // preserve type of Gold
4683                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4684                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4685               }
4686           } else {
4687             /* Move from ICS was illegal!?  Punt. */
4688             if (appData.debugMode) {
4689               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4690               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4691             }
4692             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4693             strcat(parseList[moveNum - 1], " ");
4694             strcat(parseList[moveNum - 1], elapsed_time);
4695             moveList[moveNum - 1][0] = NULLCHAR;
4696             fromX = fromY = toX = toY = -1;
4697           }
4698         }
4699   if (appData.debugMode) {
4700     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4701     setbuf(debugFP, NULL);
4702   }
4703
4704 #if ZIPPY
4705         /* Send move to chess program (BEFORE animating it). */
4706         if (appData.zippyPlay && !newGame && newMove &&
4707            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4708
4709             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4710                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4711                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4712                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4713                             move_str);
4714                     DisplayError(str, 0);
4715                 } else {
4716                     if (first.sendTime) {
4717                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4718                     }
4719                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4720                     if (firstMove && !bookHit) {
4721                         firstMove = FALSE;
4722                         if (first.useColors) {
4723                           SendToProgram(gameMode == IcsPlayingWhite ?
4724                                         "white\ngo\n" :
4725                                         "black\ngo\n", &first);
4726                         } else {
4727                           SendToProgram("go\n", &first);
4728                         }
4729                         first.maybeThinking = TRUE;
4730                     }
4731                 }
4732             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4733               if (moveList[moveNum - 1][0] == NULLCHAR) {
4734                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4735                 DisplayError(str, 0);
4736               } else {
4737                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4738                 SendMoveToProgram(moveNum - 1, &first);
4739               }
4740             }
4741         }
4742 #endif
4743     }
4744
4745     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4746         /* If move comes from a remote source, animate it.  If it
4747            isn't remote, it will have already been animated. */
4748         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4749             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4750         }
4751         if (!pausing && appData.highlightLastMove) {
4752             SetHighlights(fromX, fromY, toX, toY);
4753         }
4754     }
4755
4756     /* Start the clocks */
4757     whiteFlag = blackFlag = FALSE;
4758     appData.clockMode = !(basetime == 0 && increment == 0);
4759     if (ticking == 0) {
4760       ics_clock_paused = TRUE;
4761       StopClocks();
4762     } else if (ticking == 1) {
4763       ics_clock_paused = FALSE;
4764     }
4765     if (gameMode == IcsIdle ||
4766         relation == RELATION_OBSERVING_STATIC ||
4767         relation == RELATION_EXAMINING ||
4768         ics_clock_paused)
4769       DisplayBothClocks();
4770     else
4771       StartClocks();
4772
4773     /* Display opponents and material strengths */
4774     if (gameInfo.variant != VariantBughouse &&
4775         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4776         if (tinyLayout || smallLayout) {
4777             if(gameInfo.variant == VariantNormal)
4778               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4779                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4780                     basetime, increment);
4781             else
4782               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4783                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4784                     basetime, increment, (int) gameInfo.variant);
4785         } else {
4786             if(gameInfo.variant == VariantNormal)
4787               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4788                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4789                     basetime, increment);
4790             else
4791               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4792                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4793                     basetime, increment, VariantName(gameInfo.variant));
4794         }
4795         DisplayTitle(str);
4796   if (appData.debugMode) {
4797     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4798   }
4799     }
4800
4801
4802     /* Display the board */
4803     if (!pausing && !appData.noGUI) {
4804
4805       if (appData.premove)
4806           if (!gotPremove ||
4807              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4808              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4809               ClearPremoveHighlights();
4810
4811       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4812         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4813       DrawPosition(j, boards[currentMove]);
4814
4815       DisplayMove(moveNum - 1);
4816       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4817             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4818               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4819         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4820       }
4821     }
4822
4823     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4824 #if ZIPPY
4825     if(bookHit) { // [HGM] book: simulate book reply
4826         static char bookMove[MSG_SIZ]; // a bit generous?
4827
4828         programStats.nodes = programStats.depth = programStats.time =
4829         programStats.score = programStats.got_only_move = 0;
4830         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4831
4832         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4833         strcat(bookMove, bookHit);
4834         HandleMachineMove(bookMove, &first);
4835     }
4836 #endif
4837 }
4838
4839 void
4840 GetMoveListEvent ()
4841 {
4842     char buf[MSG_SIZ];
4843     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4844         ics_getting_history = H_REQUESTED;
4845         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4846         SendToICS(buf);
4847     }
4848 }
4849
4850 void
4851 AnalysisPeriodicEvent (int force)
4852 {
4853     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4854          && !force) || !appData.periodicUpdates)
4855       return;
4856
4857     /* Send . command to Crafty to collect stats */
4858     SendToProgram(".\n", &first);
4859
4860     /* Don't send another until we get a response (this makes
4861        us stop sending to old Crafty's which don't understand
4862        the "." command (sending illegal cmds resets node count & time,
4863        which looks bad)) */
4864     programStats.ok_to_send = 0;
4865 }
4866
4867 void
4868 ics_update_width (int new_width)
4869 {
4870         ics_printf("set width %d\n", new_width);
4871 }
4872
4873 void
4874 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4875 {
4876     char buf[MSG_SIZ];
4877
4878     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4879         // null move in variant where engine does not understand it (for analysis purposes)
4880         SendBoard(cps, moveNum + 1); // send position after move in stead.
4881         return;
4882     }
4883     if (cps->useUsermove) {
4884       SendToProgram("usermove ", cps);
4885     }
4886     if (cps->useSAN) {
4887       char *space;
4888       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4889         int len = space - parseList[moveNum];
4890         memcpy(buf, parseList[moveNum], len);
4891         buf[len++] = '\n';
4892         buf[len] = NULLCHAR;
4893       } else {
4894         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4895       }
4896       SendToProgram(buf, cps);
4897     } else {
4898       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4899         AlphaRank(moveList[moveNum], 4);
4900         SendToProgram(moveList[moveNum], cps);
4901         AlphaRank(moveList[moveNum], 4); // and back
4902       } else
4903       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4904        * the engine. It would be nice to have a better way to identify castle
4905        * moves here. */
4906       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4907                                                                          && cps->useOOCastle) {
4908         int fromX = moveList[moveNum][0] - AAA;
4909         int fromY = moveList[moveNum][1] - ONE;
4910         int toX = moveList[moveNum][2] - AAA;
4911         int toY = moveList[moveNum][3] - ONE;
4912         if((boards[moveNum][fromY][fromX] == WhiteKing
4913             && boards[moveNum][toY][toX] == WhiteRook)
4914            || (boards[moveNum][fromY][fromX] == BlackKing
4915                && boards[moveNum][toY][toX] == BlackRook)) {
4916           if(toX > fromX) SendToProgram("O-O\n", cps);
4917           else SendToProgram("O-O-O\n", cps);
4918         }
4919         else SendToProgram(moveList[moveNum], cps);
4920       } else
4921       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4922         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4923           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4924           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4925                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4926         } else
4927           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4928                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4929         SendToProgram(buf, cps);
4930       }
4931       else SendToProgram(moveList[moveNum], cps);
4932       /* End of additions by Tord */
4933     }
4934
4935     /* [HGM] setting up the opening has brought engine in force mode! */
4936     /*       Send 'go' if we are in a mode where machine should play. */
4937     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4938         (gameMode == TwoMachinesPlay   ||
4939 #if ZIPPY
4940          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4941 #endif
4942          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4943         SendToProgram("go\n", cps);
4944   if (appData.debugMode) {
4945     fprintf(debugFP, "(extra)\n");
4946   }
4947     }
4948     setboardSpoiledMachineBlack = 0;
4949 }
4950
4951 void
4952 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4953 {
4954     char user_move[MSG_SIZ];
4955     char suffix[4];
4956
4957     if(gameInfo.variant == VariantSChess && promoChar) {
4958         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4959         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4960     } else suffix[0] = NULLCHAR;
4961
4962     switch (moveType) {
4963       default:
4964         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4965                 (int)moveType, fromX, fromY, toX, toY);
4966         DisplayError(user_move + strlen("say "), 0);
4967         break;
4968       case WhiteKingSideCastle:
4969       case BlackKingSideCastle:
4970       case WhiteQueenSideCastleWild:
4971       case BlackQueenSideCastleWild:
4972       /* PUSH Fabien */
4973       case WhiteHSideCastleFR:
4974       case BlackHSideCastleFR:
4975       /* POP Fabien */
4976         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4977         break;
4978       case WhiteQueenSideCastle:
4979       case BlackQueenSideCastle:
4980       case WhiteKingSideCastleWild:
4981       case BlackKingSideCastleWild:
4982       /* PUSH Fabien */
4983       case WhiteASideCastleFR:
4984       case BlackASideCastleFR:
4985       /* POP Fabien */
4986         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
4987         break;
4988       case WhiteNonPromotion:
4989       case BlackNonPromotion:
4990         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4991         break;
4992       case WhitePromotion:
4993       case BlackPromotion:
4994         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4995           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4996                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4997                 PieceToChar(WhiteFerz));
4998         else if(gameInfo.variant == VariantGreat)
4999           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5000                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5001                 PieceToChar(WhiteMan));
5002         else
5003           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5004                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5005                 promoChar);
5006         break;
5007       case WhiteDrop:
5008       case BlackDrop:
5009       drop:
5010         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5011                  ToUpper(PieceToChar((ChessSquare) fromX)),
5012                  AAA + toX, ONE + toY);
5013         break;
5014       case IllegalMove:  /* could be a variant we don't quite understand */
5015         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5016       case NormalMove:
5017       case WhiteCapturesEnPassant:
5018       case BlackCapturesEnPassant:
5019         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5020                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5021         break;
5022     }
5023     SendToICS(user_move);
5024     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5025         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5026 }
5027
5028 void
5029 UploadGameEvent ()
5030 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5031     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5032     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5033     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5034       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5035       return;
5036     }
5037     if(gameMode != IcsExamining) { // is this ever not the case?
5038         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5039
5040         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5041           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5042         } else { // on FICS we must first go to general examine mode
5043           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5044         }
5045         if(gameInfo.variant != VariantNormal) {
5046             // try figure out wild number, as xboard names are not always valid on ICS
5047             for(i=1; i<=36; i++) {
5048               snprintf(buf, MSG_SIZ, "wild/%d", i);
5049                 if(StringToVariant(buf) == gameInfo.variant) break;
5050             }
5051             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5052             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5053             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5054         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5055         SendToICS(ics_prefix);
5056         SendToICS(buf);
5057         if(startedFromSetupPosition || backwardMostMove != 0) {
5058           fen = PositionToFEN(backwardMostMove, NULL);
5059           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5060             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5061             SendToICS(buf);
5062           } else { // FICS: everything has to set by separate bsetup commands
5063             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5064             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5065             SendToICS(buf);
5066             if(!WhiteOnMove(backwardMostMove)) {
5067                 SendToICS("bsetup tomove black\n");
5068             }
5069             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5070             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5071             SendToICS(buf);
5072             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5073             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5074             SendToICS(buf);
5075             i = boards[backwardMostMove][EP_STATUS];
5076             if(i >= 0) { // set e.p.
5077               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5078                 SendToICS(buf);
5079             }
5080             bsetup++;
5081           }
5082         }
5083       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5084             SendToICS("bsetup done\n"); // switch to normal examining.
5085     }
5086     for(i = backwardMostMove; i<last; i++) {
5087         char buf[20];
5088         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5089         SendToICS(buf);
5090     }
5091     SendToICS(ics_prefix);
5092     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5093 }
5094
5095 void
5096 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5097 {
5098     if (rf == DROP_RANK) {
5099       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5100       sprintf(move, "%c@%c%c\n",
5101                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5102     } else {
5103         if (promoChar == 'x' || promoChar == NULLCHAR) {
5104           sprintf(move, "%c%c%c%c\n",
5105                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5106         } else {
5107             sprintf(move, "%c%c%c%c%c\n",
5108                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5109         }
5110     }
5111 }
5112
5113 void
5114 ProcessICSInitScript (FILE *f)
5115 {
5116     char buf[MSG_SIZ];
5117
5118     while (fgets(buf, MSG_SIZ, f)) {
5119         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5120     }
5121
5122     fclose(f);
5123 }
5124
5125
5126 static int lastX, lastY, selectFlag, dragging;
5127
5128 void
5129 Sweep (int step)
5130 {
5131     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5132     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5133     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5134     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5135     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5136     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5137     do {
5138         promoSweep -= step;
5139         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5140         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5141         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5142         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5143         if(!step) step = -1;
5144     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5145             appData.testLegality && (promoSweep == king ||
5146             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5147     ChangeDragPiece(promoSweep);
5148 }
5149
5150 int
5151 PromoScroll (int x, int y)
5152 {
5153   int step = 0;
5154
5155   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5156   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5157   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5158   if(!step) return FALSE;
5159   lastX = x; lastY = y;
5160   if((promoSweep < BlackPawn) == flipView) step = -step;
5161   if(step > 0) selectFlag = 1;
5162   if(!selectFlag) Sweep(step);
5163   return FALSE;
5164 }
5165
5166 void
5167 NextPiece (int step)
5168 {
5169     ChessSquare piece = boards[currentMove][toY][toX];
5170     do {
5171         pieceSweep -= step;
5172         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5173         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5174         if(!step) step = -1;
5175     } while(PieceToChar(pieceSweep) == '.');
5176     boards[currentMove][toY][toX] = pieceSweep;
5177     DrawPosition(FALSE, boards[currentMove]);
5178     boards[currentMove][toY][toX] = piece;
5179 }
5180 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5181 void
5182 AlphaRank (char *move, int n)
5183 {
5184 //    char *p = move, c; int x, y;
5185
5186     if (appData.debugMode) {
5187         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5188     }
5189
5190     if(move[1]=='*' &&
5191        move[2]>='0' && move[2]<='9' &&
5192        move[3]>='a' && move[3]<='x'    ) {
5193         move[1] = '@';
5194         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5195         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5196     } else
5197     if(move[0]>='0' && move[0]<='9' &&
5198        move[1]>='a' && move[1]<='x' &&
5199        move[2]>='0' && move[2]<='9' &&
5200        move[3]>='a' && move[3]<='x'    ) {
5201         /* input move, Shogi -> normal */
5202         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5203         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5204         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5205         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5206     } else
5207     if(move[1]=='@' &&
5208        move[3]>='0' && move[3]<='9' &&
5209        move[2]>='a' && move[2]<='x'    ) {
5210         move[1] = '*';
5211         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5212         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5213     } else
5214     if(
5215        move[0]>='a' && move[0]<='x' &&
5216        move[3]>='0' && move[3]<='9' &&
5217        move[2]>='a' && move[2]<='x'    ) {
5218          /* output move, normal -> Shogi */
5219         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5220         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5221         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5222         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5223         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5224     }
5225     if (appData.debugMode) {
5226         fprintf(debugFP, "   out = '%s'\n", move);
5227     }
5228 }
5229
5230 char yy_textstr[8000];
5231
5232 /* Parser for moves from gnuchess, ICS, or user typein box */
5233 Boolean
5234 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5235 {
5236     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5237
5238     switch (*moveType) {
5239       case WhitePromotion:
5240       case BlackPromotion:
5241       case WhiteNonPromotion:
5242       case BlackNonPromotion:
5243       case NormalMove:
5244       case WhiteCapturesEnPassant:
5245       case BlackCapturesEnPassant:
5246       case WhiteKingSideCastle:
5247       case WhiteQueenSideCastle:
5248       case BlackKingSideCastle:
5249       case BlackQueenSideCastle:
5250       case WhiteKingSideCastleWild:
5251       case WhiteQueenSideCastleWild:
5252       case BlackKingSideCastleWild:
5253       case BlackQueenSideCastleWild:
5254       /* Code added by Tord: */
5255       case WhiteHSideCastleFR:
5256       case WhiteASideCastleFR:
5257       case BlackHSideCastleFR:
5258       case BlackASideCastleFR:
5259       /* End of code added by Tord */
5260       case IllegalMove:         /* bug or odd chess variant */
5261         *fromX = currentMoveString[0] - AAA;
5262         *fromY = currentMoveString[1] - ONE;
5263         *toX = currentMoveString[2] - AAA;
5264         *toY = currentMoveString[3] - ONE;
5265         *promoChar = currentMoveString[4];
5266         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5267             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5268     if (appData.debugMode) {
5269         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5270     }
5271             *fromX = *fromY = *toX = *toY = 0;
5272             return FALSE;
5273         }
5274         if (appData.testLegality) {
5275           return (*moveType != IllegalMove);
5276         } else {
5277           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5278                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5279         }
5280
5281       case WhiteDrop:
5282       case BlackDrop:
5283         *fromX = *moveType == WhiteDrop ?
5284           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5285           (int) CharToPiece(ToLower(currentMoveString[0]));
5286         *fromY = DROP_RANK;
5287         *toX = currentMoveString[2] - AAA;
5288         *toY = currentMoveString[3] - ONE;
5289         *promoChar = NULLCHAR;
5290         return TRUE;
5291
5292       case AmbiguousMove:
5293       case ImpossibleMove:
5294       case EndOfFile:
5295       case ElapsedTime:
5296       case Comment:
5297       case PGNTag:
5298       case NAG:
5299       case WhiteWins:
5300       case BlackWins:
5301       case GameIsDrawn:
5302       default:
5303     if (appData.debugMode) {
5304         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5305     }
5306         /* bug? */
5307         *fromX = *fromY = *toX = *toY = 0;
5308         *promoChar = NULLCHAR;
5309         return FALSE;
5310     }
5311 }
5312
5313 Boolean pushed = FALSE;
5314 char *lastParseAttempt;
5315
5316 void
5317 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5318 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5319   int fromX, fromY, toX, toY; char promoChar;
5320   ChessMove moveType;
5321   Boolean valid;
5322   int nr = 0;
5323
5324   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5325     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5326     pushed = TRUE;
5327   }
5328   endPV = forwardMostMove;
5329   do {
5330     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5331     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5332     lastParseAttempt = pv;
5333     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5334 if(appData.debugMode){
5335 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
5336 }
5337     if(!valid && nr == 0 &&
5338        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5339         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5340         // Hande case where played move is different from leading PV move
5341         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5342         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5343         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5344         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5345           endPV += 2; // if position different, keep this
5346           moveList[endPV-1][0] = fromX + AAA;
5347           moveList[endPV-1][1] = fromY + ONE;
5348           moveList[endPV-1][2] = toX + AAA;
5349           moveList[endPV-1][3] = toY + ONE;
5350           parseList[endPV-1][0] = NULLCHAR;
5351           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5352         }
5353       }
5354     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5355     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5356     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5357     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5358         valid++; // allow comments in PV
5359         continue;
5360     }
5361     nr++;
5362     if(endPV+1 > framePtr) break; // no space, truncate
5363     if(!valid) break;
5364     endPV++;
5365     CopyBoard(boards[endPV], boards[endPV-1]);
5366     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5367     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5368     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5369     CoordsToAlgebraic(boards[endPV - 1],
5370                              PosFlags(endPV - 1),
5371                              fromY, fromX, toY, toX, promoChar,
5372                              parseList[endPV - 1]);
5373   } while(valid);
5374   if(atEnd == 2) return; // used hidden, for PV conversion
5375   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5376   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5377   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5378                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5379   DrawPosition(TRUE, boards[currentMove]);
5380 }
5381
5382 int
5383 MultiPV (ChessProgramState *cps)
5384 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5385         int i;
5386         for(i=0; i<cps->nrOptions; i++)
5387             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5388                 return i;
5389         return -1;
5390 }
5391
5392 Boolean
5393 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5394 {
5395         int startPV, multi, lineStart, origIndex = index;
5396         char *p, buf2[MSG_SIZ];
5397
5398         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5399         lastX = x; lastY = y;
5400         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5401         lineStart = startPV = index;
5402         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5403         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5404         index = startPV;
5405         do{ while(buf[index] && buf[index] != '\n') index++;
5406         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5407         buf[index] = 0;
5408         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5409                 int n = first.option[multi].value;
5410                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5411                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5412                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5413                 first.option[multi].value = n;
5414                 *start = *end = 0;
5415                 return FALSE;
5416         }
5417         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5418         *start = startPV; *end = index-1;
5419         return TRUE;
5420 }
5421
5422 char *
5423 PvToSAN (char *pv)
5424 {
5425         static char buf[10*MSG_SIZ];
5426         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5427         *buf = NULLCHAR;
5428         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5429         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5430         for(i = forwardMostMove; i<endPV; i++){
5431             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5432             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5433             k += strlen(buf+k);
5434         }
5435         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5436         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5437         endPV = savedEnd;
5438         return buf;
5439 }
5440
5441 Boolean
5442 LoadPV (int x, int y)
5443 { // called on right mouse click to load PV
5444   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5445   lastX = x; lastY = y;
5446   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5447   return TRUE;
5448 }
5449
5450 void
5451 UnLoadPV ()
5452 {
5453   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5454   if(endPV < 0) return;
5455   endPV = -1;
5456   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5457         Boolean saveAnimate = appData.animate;
5458         if(pushed) {
5459             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5460                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5461             } else storedGames--; // abandon shelved tail of original game
5462         }
5463         pushed = FALSE;
5464         forwardMostMove = currentMove;
5465         currentMove = oldFMM;
5466         appData.animate = FALSE;
5467         ToNrEvent(forwardMostMove);
5468         appData.animate = saveAnimate;
5469   }
5470   currentMove = forwardMostMove;
5471   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5472   ClearPremoveHighlights();
5473   DrawPosition(TRUE, boards[currentMove]);
5474 }
5475
5476 void
5477 MovePV (int x, int y, int h)
5478 { // step through PV based on mouse coordinates (called on mouse move)
5479   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5480
5481   // we must somehow check if right button is still down (might be released off board!)
5482   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5483   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5484   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5485   if(!step) return;
5486   lastX = x; lastY = y;
5487
5488   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5489   if(endPV < 0) return;
5490   if(y < margin) step = 1; else
5491   if(y > h - margin) step = -1;
5492   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5493   currentMove += step;
5494   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5495   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5496                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5497   DrawPosition(FALSE, boards[currentMove]);
5498 }
5499
5500
5501 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5502 // All positions will have equal probability, but the current method will not provide a unique
5503 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5504 #define DARK 1
5505 #define LITE 2
5506 #define ANY 3
5507
5508 int squaresLeft[4];
5509 int piecesLeft[(int)BlackPawn];
5510 int seed, nrOfShuffles;
5511
5512 void
5513 GetPositionNumber ()
5514 {       // sets global variable seed
5515         int i;
5516
5517         seed = appData.defaultFrcPosition;
5518         if(seed < 0) { // randomize based on time for negative FRC position numbers
5519                 for(i=0; i<50; i++) seed += random();
5520                 seed = random() ^ random() >> 8 ^ random() << 8;
5521                 if(seed<0) seed = -seed;
5522         }
5523 }
5524
5525 int
5526 put (Board board, int pieceType, int rank, int n, int shade)
5527 // put the piece on the (n-1)-th empty squares of the given shade
5528 {
5529         int i;
5530
5531         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5532                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5533                         board[rank][i] = (ChessSquare) pieceType;
5534                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5535                         squaresLeft[ANY]--;
5536                         piecesLeft[pieceType]--;
5537                         return i;
5538                 }
5539         }
5540         return -1;
5541 }
5542
5543
5544 void
5545 AddOnePiece (Board board, int pieceType, int rank, int shade)
5546 // calculate where the next piece goes, (any empty square), and put it there
5547 {
5548         int i;
5549
5550         i = seed % squaresLeft[shade];
5551         nrOfShuffles *= squaresLeft[shade];
5552         seed /= squaresLeft[shade];
5553         put(board, pieceType, rank, i, shade);
5554 }
5555
5556 void
5557 AddTwoPieces (Board board, int pieceType, int rank)
5558 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5559 {
5560         int i, n=squaresLeft[ANY], j=n-1, k;
5561
5562         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5563         i = seed % k;  // pick one
5564         nrOfShuffles *= k;
5565         seed /= k;
5566         while(i >= j) i -= j--;
5567         j = n - 1 - j; i += j;
5568         put(board, pieceType, rank, j, ANY);
5569         put(board, pieceType, rank, i, ANY);
5570 }
5571
5572 void
5573 SetUpShuffle (Board board, int number)
5574 {
5575         int i, p, first=1;
5576
5577         GetPositionNumber(); nrOfShuffles = 1;
5578
5579         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5580         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5581         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5582
5583         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5584
5585         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5586             p = (int) board[0][i];
5587             if(p < (int) BlackPawn) piecesLeft[p] ++;
5588             board[0][i] = EmptySquare;
5589         }
5590
5591         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5592             // shuffles restricted to allow normal castling put KRR first
5593             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5594                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5595             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5596                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5597             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5598                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5599             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5600                 put(board, WhiteRook, 0, 0, ANY);
5601             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5602         }
5603
5604         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5605             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5606             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5607                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5608                 while(piecesLeft[p] >= 2) {
5609                     AddOnePiece(board, p, 0, LITE);
5610                     AddOnePiece(board, p, 0, DARK);
5611                 }
5612                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5613             }
5614
5615         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5616             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5617             // but we leave King and Rooks for last, to possibly obey FRC restriction
5618             if(p == (int)WhiteRook) continue;
5619             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5620             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5621         }
5622
5623         // now everything is placed, except perhaps King (Unicorn) and Rooks
5624
5625         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5626             // Last King gets castling rights
5627             while(piecesLeft[(int)WhiteUnicorn]) {
5628                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5629                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5630             }
5631
5632             while(piecesLeft[(int)WhiteKing]) {
5633                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5634                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5635             }
5636
5637
5638         } else {
5639             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5640             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5641         }
5642
5643         // Only Rooks can be left; simply place them all
5644         while(piecesLeft[(int)WhiteRook]) {
5645                 i = put(board, WhiteRook, 0, 0, ANY);
5646                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5647                         if(first) {
5648                                 first=0;
5649                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5650                         }
5651                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5652                 }
5653         }
5654         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5655             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5656         }
5657
5658         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5659 }
5660
5661 int
5662 SetCharTable (char *table, const char * map)
5663 /* [HGM] moved here from winboard.c because of its general usefulness */
5664 /*       Basically a safe strcpy that uses the last character as King */
5665 {
5666     int result = FALSE; int NrPieces;
5667
5668     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5669                     && NrPieces >= 12 && !(NrPieces&1)) {
5670         int i; /* [HGM] Accept even length from 12 to 34 */
5671
5672         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5673         for( i=0; i<NrPieces/2-1; i++ ) {
5674             table[i] = map[i];
5675             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5676         }
5677         table[(int) WhiteKing]  = map[NrPieces/2-1];
5678         table[(int) BlackKing]  = map[NrPieces-1];
5679
5680         result = TRUE;
5681     }
5682
5683     return result;
5684 }
5685
5686 void
5687 Prelude (Board board)
5688 {       // [HGM] superchess: random selection of exo-pieces
5689         int i, j, k; ChessSquare p;
5690         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5691
5692         GetPositionNumber(); // use FRC position number
5693
5694         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5695             SetCharTable(pieceToChar, appData.pieceToCharTable);
5696             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5697                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5698         }
5699
5700         j = seed%4;                 seed /= 4;
5701         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5702         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5703         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5704         j = seed%3 + (seed%3 >= j); seed /= 3;
5705         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5706         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5707         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5708         j = seed%3;                 seed /= 3;
5709         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5710         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5711         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5712         j = seed%2 + (seed%2 >= j); seed /= 2;
5713         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5714         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5715         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5716         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5717         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5718         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5719         put(board, exoPieces[0],    0, 0, ANY);
5720         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5721 }
5722
5723 void
5724 InitPosition (int redraw)
5725 {
5726     ChessSquare (* pieces)[BOARD_FILES];
5727     int i, j, pawnRow, overrule,
5728     oldx = gameInfo.boardWidth,
5729     oldy = gameInfo.boardHeight,
5730     oldh = gameInfo.holdingsWidth;
5731     static int oldv;
5732
5733     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5734
5735     /* [AS] Initialize pv info list [HGM] and game status */
5736     {
5737         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5738             pvInfoList[i].depth = 0;
5739             boards[i][EP_STATUS] = EP_NONE;
5740             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5741         }
5742
5743         initialRulePlies = 0; /* 50-move counter start */
5744
5745         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5746         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5747     }
5748
5749
5750     /* [HGM] logic here is completely changed. In stead of full positions */
5751     /* the initialized data only consist of the two backranks. The switch */
5752     /* selects which one we will use, which is than copied to the Board   */
5753     /* initialPosition, which for the rest is initialized by Pawns and    */
5754     /* empty squares. This initial position is then copied to boards[0],  */
5755     /* possibly after shuffling, so that it remains available.            */
5756
5757     gameInfo.holdingsWidth = 0; /* default board sizes */
5758     gameInfo.boardWidth    = 8;
5759     gameInfo.boardHeight   = 8;
5760     gameInfo.holdingsSize  = 0;
5761     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5762     for(i=0; i<BOARD_FILES-2; i++)
5763       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5764     initialPosition[EP_STATUS] = EP_NONE;
5765     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5766     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5767          SetCharTable(pieceNickName, appData.pieceNickNames);
5768     else SetCharTable(pieceNickName, "............");
5769     pieces = FIDEArray;
5770
5771     switch (gameInfo.variant) {
5772     case VariantFischeRandom:
5773       shuffleOpenings = TRUE;
5774     default:
5775       break;
5776     case VariantShatranj:
5777       pieces = ShatranjArray;
5778       nrCastlingRights = 0;
5779       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5780       break;
5781     case VariantMakruk:
5782       pieces = makrukArray;
5783       nrCastlingRights = 0;
5784       startedFromSetupPosition = TRUE;
5785       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5786       break;
5787     case VariantTwoKings:
5788       pieces = twoKingsArray;
5789       break;
5790     case VariantGrand:
5791       pieces = GrandArray;
5792       nrCastlingRights = 0;
5793       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5794       gameInfo.boardWidth = 10;
5795       gameInfo.boardHeight = 10;
5796       gameInfo.holdingsSize = 7;
5797       break;
5798     case VariantCapaRandom:
5799       shuffleOpenings = TRUE;
5800     case VariantCapablanca:
5801       pieces = CapablancaArray;
5802       gameInfo.boardWidth = 10;
5803       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5804       break;
5805     case VariantGothic:
5806       pieces = GothicArray;
5807       gameInfo.boardWidth = 10;
5808       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5809       break;
5810     case VariantSChess:
5811       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5812       gameInfo.holdingsSize = 7;
5813       break;
5814     case VariantJanus:
5815       pieces = JanusArray;
5816       gameInfo.boardWidth = 10;
5817       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5818       nrCastlingRights = 6;
5819         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5820         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5821         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5822         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5823         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5824         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5825       break;
5826     case VariantFalcon:
5827       pieces = FalconArray;
5828       gameInfo.boardWidth = 10;
5829       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5830       break;
5831     case VariantXiangqi:
5832       pieces = XiangqiArray;
5833       gameInfo.boardWidth  = 9;
5834       gameInfo.boardHeight = 10;
5835       nrCastlingRights = 0;
5836       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5837       break;
5838     case VariantShogi:
5839       pieces = ShogiArray;
5840       gameInfo.boardWidth  = 9;
5841       gameInfo.boardHeight = 9;
5842       gameInfo.holdingsSize = 7;
5843       nrCastlingRights = 0;
5844       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5845       break;
5846     case VariantCourier:
5847       pieces = CourierArray;
5848       gameInfo.boardWidth  = 12;
5849       nrCastlingRights = 0;
5850       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5851       break;
5852     case VariantKnightmate:
5853       pieces = KnightmateArray;
5854       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5855       break;
5856     case VariantSpartan:
5857       pieces = SpartanArray;
5858       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5859       break;
5860     case VariantFairy:
5861       pieces = fairyArray;
5862       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5863       break;
5864     case VariantGreat:
5865       pieces = GreatArray;
5866       gameInfo.boardWidth = 10;
5867       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5868       gameInfo.holdingsSize = 8;
5869       break;
5870     case VariantSuper:
5871       pieces = FIDEArray;
5872       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5873       gameInfo.holdingsSize = 8;
5874       startedFromSetupPosition = TRUE;
5875       break;
5876     case VariantCrazyhouse:
5877     case VariantBughouse:
5878       pieces = FIDEArray;
5879       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5880       gameInfo.holdingsSize = 5;
5881       break;
5882     case VariantWildCastle:
5883       pieces = FIDEArray;
5884       /* !!?shuffle with kings guaranteed to be on d or e file */
5885       shuffleOpenings = 1;
5886       break;
5887     case VariantNoCastle:
5888       pieces = FIDEArray;
5889       nrCastlingRights = 0;
5890       /* !!?unconstrained back-rank shuffle */
5891       shuffleOpenings = 1;
5892       break;
5893     }
5894
5895     overrule = 0;
5896     if(appData.NrFiles >= 0) {
5897         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5898         gameInfo.boardWidth = appData.NrFiles;
5899     }
5900     if(appData.NrRanks >= 0) {
5901         gameInfo.boardHeight = appData.NrRanks;
5902     }
5903     if(appData.holdingsSize >= 0) {
5904         i = appData.holdingsSize;
5905         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5906         gameInfo.holdingsSize = i;
5907     }
5908     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5909     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5910         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5911
5912     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5913     if(pawnRow < 1) pawnRow = 1;
5914     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5915
5916     /* User pieceToChar list overrules defaults */
5917     if(appData.pieceToCharTable != NULL)
5918         SetCharTable(pieceToChar, appData.pieceToCharTable);
5919
5920     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5921
5922         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5923             s = (ChessSquare) 0; /* account holding counts in guard band */
5924         for( i=0; i<BOARD_HEIGHT; i++ )
5925             initialPosition[i][j] = s;
5926
5927         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5928         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5929         initialPosition[pawnRow][j] = WhitePawn;
5930         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5931         if(gameInfo.variant == VariantXiangqi) {
5932             if(j&1) {
5933                 initialPosition[pawnRow][j] =
5934                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5935                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5936                    initialPosition[2][j] = WhiteCannon;
5937                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5938                 }
5939             }
5940         }
5941         if(gameInfo.variant == VariantGrand) {
5942             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5943                initialPosition[0][j] = WhiteRook;
5944                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5945             }
5946         }
5947         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5948     }
5949     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5950
5951             j=BOARD_LEFT+1;
5952             initialPosition[1][j] = WhiteBishop;
5953             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5954             j=BOARD_RGHT-2;
5955             initialPosition[1][j] = WhiteRook;
5956             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5957     }
5958
5959     if( nrCastlingRights == -1) {
5960         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5961         /*       This sets default castling rights from none to normal corners   */
5962         /* Variants with other castling rights must set them themselves above    */
5963         nrCastlingRights = 6;
5964
5965         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5966         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5967         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5968         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5969         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5970         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5971      }
5972
5973      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5974      if(gameInfo.variant == VariantGreat) { // promotion commoners
5975         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5976         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5977         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5978         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5979      }
5980      if( gameInfo.variant == VariantSChess ) {
5981       initialPosition[1][0] = BlackMarshall;
5982       initialPosition[2][0] = BlackAngel;
5983       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5984       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5985       initialPosition[1][1] = initialPosition[2][1] = 
5986       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5987      }
5988   if (appData.debugMode) {
5989     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5990   }
5991     if(shuffleOpenings) {
5992         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5993         startedFromSetupPosition = TRUE;
5994     }
5995     if(startedFromPositionFile) {
5996       /* [HGM] loadPos: use PositionFile for every new game */
5997       CopyBoard(initialPosition, filePosition);
5998       for(i=0; i<nrCastlingRights; i++)
5999           initialRights[i] = filePosition[CASTLING][i];
6000       startedFromSetupPosition = TRUE;
6001     }
6002
6003     CopyBoard(boards[0], initialPosition);
6004
6005     if(oldx != gameInfo.boardWidth ||
6006        oldy != gameInfo.boardHeight ||
6007        oldv != gameInfo.variant ||
6008        oldh != gameInfo.holdingsWidth
6009                                          )
6010             InitDrawingSizes(-2 ,0);
6011
6012     oldv = gameInfo.variant;
6013     if (redraw)
6014       DrawPosition(TRUE, boards[currentMove]);
6015 }
6016
6017 void
6018 SendBoard (ChessProgramState *cps, int moveNum)
6019 {
6020     char message[MSG_SIZ];
6021
6022     if (cps->useSetboard) {
6023       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6024       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6025       SendToProgram(message, cps);
6026       free(fen);
6027
6028     } else {
6029       ChessSquare *bp;
6030       int i, j, left=0, right=BOARD_WIDTH;
6031       /* Kludge to set black to move, avoiding the troublesome and now
6032        * deprecated "black" command.
6033        */
6034       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6035         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6036
6037       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6038
6039       SendToProgram("edit\n", cps);
6040       SendToProgram("#\n", cps);
6041       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6042         bp = &boards[moveNum][i][left];
6043         for (j = left; j < right; j++, bp++) {
6044           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6045           if ((int) *bp < (int) BlackPawn) {
6046             if(j == BOARD_RGHT+1)
6047                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6048             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6049             if(message[0] == '+' || message[0] == '~') {
6050               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6051                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6052                         AAA + j, ONE + i);
6053             }
6054             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6055                 message[1] = BOARD_RGHT   - 1 - j + '1';
6056                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6057             }
6058             SendToProgram(message, cps);
6059           }
6060         }
6061       }
6062
6063       SendToProgram("c\n", cps);
6064       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6065         bp = &boards[moveNum][i][left];
6066         for (j = left; j < right; j++, bp++) {
6067           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6068           if (((int) *bp != (int) EmptySquare)
6069               && ((int) *bp >= (int) BlackPawn)) {
6070             if(j == BOARD_LEFT-2)
6071                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6072             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6073                     AAA + j, ONE + i);
6074             if(message[0] == '+' || message[0] == '~') {
6075               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6076                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6077                         AAA + j, ONE + i);
6078             }
6079             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6080                 message[1] = BOARD_RGHT   - 1 - j + '1';
6081                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6082             }
6083             SendToProgram(message, cps);
6084           }
6085         }
6086       }
6087
6088       SendToProgram(".\n", cps);
6089     }
6090     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6091 }
6092
6093 ChessSquare
6094 DefaultPromoChoice (int white)
6095 {
6096     ChessSquare result;
6097     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6098         result = WhiteFerz; // no choice
6099     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6100         result= WhiteKing; // in Suicide Q is the last thing we want
6101     else if(gameInfo.variant == VariantSpartan)
6102         result = white ? WhiteQueen : WhiteAngel;
6103     else result = WhiteQueen;
6104     if(!white) result = WHITE_TO_BLACK result;
6105     return result;
6106 }
6107
6108 static int autoQueen; // [HGM] oneclick
6109
6110 int
6111 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6112 {
6113     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6114     /* [HGM] add Shogi promotions */
6115     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6116     ChessSquare piece;
6117     ChessMove moveType;
6118     Boolean premove;
6119
6120     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6121     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6122
6123     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6124       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6125         return FALSE;
6126
6127     piece = boards[currentMove][fromY][fromX];
6128     if(gameInfo.variant == VariantShogi) {
6129         promotionZoneSize = BOARD_HEIGHT/3;
6130         highestPromotingPiece = (int)WhiteFerz;
6131     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6132         promotionZoneSize = 3;
6133     }
6134
6135     // Treat Lance as Pawn when it is not representing Amazon
6136     if(gameInfo.variant != VariantSuper) {
6137         if(piece == WhiteLance) piece = WhitePawn; else
6138         if(piece == BlackLance) piece = BlackPawn;
6139     }
6140
6141     // next weed out all moves that do not touch the promotion zone at all
6142     if((int)piece >= BlackPawn) {
6143         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6144              return FALSE;
6145         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6146     } else {
6147         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6148            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6149     }
6150
6151     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6152
6153     // weed out mandatory Shogi promotions
6154     if(gameInfo.variant == VariantShogi) {
6155         if(piece >= BlackPawn) {
6156             if(toY == 0 && piece == BlackPawn ||
6157                toY == 0 && piece == BlackQueen ||
6158                toY <= 1 && piece == BlackKnight) {
6159                 *promoChoice = '+';
6160                 return FALSE;
6161             }
6162         } else {
6163             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6164                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6165                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6166                 *promoChoice = '+';
6167                 return FALSE;
6168             }
6169         }
6170     }
6171
6172     // weed out obviously illegal Pawn moves
6173     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6174         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6175         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6176         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6177         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6178         // note we are not allowed to test for valid (non-)capture, due to premove
6179     }
6180
6181     // we either have a choice what to promote to, or (in Shogi) whether to promote
6182     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6183         *promoChoice = PieceToChar(BlackFerz);  // no choice
6184         return FALSE;
6185     }
6186     // no sense asking what we must promote to if it is going to explode...
6187     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6188         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6189         return FALSE;
6190     }
6191     // give caller the default choice even if we will not make it
6192     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6193     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6194     if(        sweepSelect && gameInfo.variant != VariantGreat
6195                            && gameInfo.variant != VariantGrand
6196                            && gameInfo.variant != VariantSuper) return FALSE;
6197     if(autoQueen) return FALSE; // predetermined
6198
6199     // suppress promotion popup on illegal moves that are not premoves
6200     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6201               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6202     if(appData.testLegality && !premove) {
6203         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6204                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6205         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6206             return FALSE;
6207     }
6208
6209     return TRUE;
6210 }
6211
6212 int
6213 InPalace (int row, int column)
6214 {   /* [HGM] for Xiangqi */
6215     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6216          column < (BOARD_WIDTH + 4)/2 &&
6217          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6218     return FALSE;
6219 }
6220
6221 int
6222 PieceForSquare (int x, int y)
6223 {
6224   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6225      return -1;
6226   else
6227      return boards[currentMove][y][x];
6228 }
6229
6230 int
6231 OKToStartUserMove (int x, int y)
6232 {
6233     ChessSquare from_piece;
6234     int white_piece;
6235
6236     if (matchMode) return FALSE;
6237     if (gameMode == EditPosition) return TRUE;
6238
6239     if (x >= 0 && y >= 0)
6240       from_piece = boards[currentMove][y][x];
6241     else
6242       from_piece = EmptySquare;
6243
6244     if (from_piece == EmptySquare) return FALSE;
6245
6246     white_piece = (int)from_piece >= (int)WhitePawn &&
6247       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6248
6249     switch (gameMode) {
6250       case AnalyzeFile:
6251       case TwoMachinesPlay:
6252       case EndOfGame:
6253         return FALSE;
6254
6255       case IcsObserving:
6256       case IcsIdle:
6257         return FALSE;
6258
6259       case MachinePlaysWhite:
6260       case IcsPlayingBlack:
6261         if (appData.zippyPlay) return FALSE;
6262         if (white_piece) {
6263             DisplayMoveError(_("You are playing Black"));
6264             return FALSE;
6265         }
6266         break;
6267
6268       case MachinePlaysBlack:
6269       case IcsPlayingWhite:
6270         if (appData.zippyPlay) return FALSE;
6271         if (!white_piece) {
6272             DisplayMoveError(_("You are playing White"));
6273             return FALSE;
6274         }
6275         break;
6276
6277       case PlayFromGameFile:
6278             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6279       case EditGame:
6280         if (!white_piece && WhiteOnMove(currentMove)) {
6281             DisplayMoveError(_("It is White's turn"));
6282             return FALSE;
6283         }
6284         if (white_piece && !WhiteOnMove(currentMove)) {
6285             DisplayMoveError(_("It is Black's turn"));
6286             return FALSE;
6287         }
6288         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6289             /* Editing correspondence game history */
6290             /* Could disallow this or prompt for confirmation */
6291             cmailOldMove = -1;
6292         }
6293         break;
6294
6295       case BeginningOfGame:
6296         if (appData.icsActive) return FALSE;
6297         if (!appData.noChessProgram) {
6298             if (!white_piece) {
6299                 DisplayMoveError(_("You are playing White"));
6300                 return FALSE;
6301             }
6302         }
6303         break;
6304
6305       case Training:
6306         if (!white_piece && WhiteOnMove(currentMove)) {
6307             DisplayMoveError(_("It is White's turn"));
6308             return FALSE;
6309         }
6310         if (white_piece && !WhiteOnMove(currentMove)) {
6311             DisplayMoveError(_("It is Black's turn"));
6312             return FALSE;
6313         }
6314         break;
6315
6316       default:
6317       case IcsExamining:
6318         break;
6319     }
6320     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6321         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6322         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6323         && gameMode != AnalyzeFile && gameMode != Training) {
6324         DisplayMoveError(_("Displayed position is not current"));
6325         return FALSE;
6326     }
6327     return TRUE;
6328 }
6329
6330 Boolean
6331 OnlyMove (int *x, int *y, Boolean captures) 
6332 {
6333     DisambiguateClosure cl;
6334     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6335     switch(gameMode) {
6336       case MachinePlaysBlack:
6337       case IcsPlayingWhite:
6338       case BeginningOfGame:
6339         if(!WhiteOnMove(currentMove)) return FALSE;
6340         break;
6341       case MachinePlaysWhite:
6342       case IcsPlayingBlack:
6343         if(WhiteOnMove(currentMove)) return FALSE;
6344         break;
6345       case EditGame:
6346         break;
6347       default:
6348         return FALSE;
6349     }
6350     cl.pieceIn = EmptySquare;
6351     cl.rfIn = *y;
6352     cl.ffIn = *x;
6353     cl.rtIn = -1;
6354     cl.ftIn = -1;
6355     cl.promoCharIn = NULLCHAR;
6356     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6357     if( cl.kind == NormalMove ||
6358         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6359         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6360         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6361       fromX = cl.ff;
6362       fromY = cl.rf;
6363       *x = cl.ft;
6364       *y = cl.rt;
6365       return TRUE;
6366     }
6367     if(cl.kind != ImpossibleMove) return FALSE;
6368     cl.pieceIn = EmptySquare;
6369     cl.rfIn = -1;
6370     cl.ffIn = -1;
6371     cl.rtIn = *y;
6372     cl.ftIn = *x;
6373     cl.promoCharIn = NULLCHAR;
6374     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6375     if( cl.kind == NormalMove ||
6376         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6377         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6378         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6379       fromX = cl.ff;
6380       fromY = cl.rf;
6381       *x = cl.ft;
6382       *y = cl.rt;
6383       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6384       return TRUE;
6385     }
6386     return FALSE;
6387 }
6388
6389 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6390 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6391 int lastLoadGameUseList = FALSE;
6392 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6393 ChessMove lastLoadGameStart = EndOfFile;
6394
6395 void
6396 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6397 {
6398     ChessMove moveType;
6399     ChessSquare pdown, pup;
6400
6401     /* Check if the user is playing in turn.  This is complicated because we
6402        let the user "pick up" a piece before it is his turn.  So the piece he
6403        tried to pick up may have been captured by the time he puts it down!
6404        Therefore we use the color the user is supposed to be playing in this
6405        test, not the color of the piece that is currently on the starting
6406        square---except in EditGame mode, where the user is playing both
6407        sides; fortunately there the capture race can't happen.  (It can
6408        now happen in IcsExamining mode, but that's just too bad.  The user
6409        will get a somewhat confusing message in that case.)
6410        */
6411
6412     switch (gameMode) {
6413       case AnalyzeFile:
6414       case TwoMachinesPlay:
6415       case EndOfGame:
6416       case IcsObserving:
6417       case IcsIdle:
6418         /* We switched into a game mode where moves are not accepted,
6419            perhaps while the mouse button was down. */
6420         return;
6421
6422       case MachinePlaysWhite:
6423         /* User is moving for Black */
6424         if (WhiteOnMove(currentMove)) {
6425             DisplayMoveError(_("It is White's turn"));
6426             return;
6427         }
6428         break;
6429
6430       case MachinePlaysBlack:
6431         /* User is moving for White */
6432         if (!WhiteOnMove(currentMove)) {
6433             DisplayMoveError(_("It is Black's turn"));
6434             return;
6435         }
6436         break;
6437
6438       case PlayFromGameFile:
6439             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6440       case EditGame:
6441       case IcsExamining:
6442       case BeginningOfGame:
6443       case AnalyzeMode:
6444       case Training:
6445         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6446         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6447             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6448             /* User is moving for Black */
6449             if (WhiteOnMove(currentMove)) {
6450                 DisplayMoveError(_("It is White's turn"));
6451                 return;
6452             }
6453         } else {
6454             /* User is moving for White */
6455             if (!WhiteOnMove(currentMove)) {
6456                 DisplayMoveError(_("It is Black's turn"));
6457                 return;
6458             }
6459         }
6460         break;
6461
6462       case IcsPlayingBlack:
6463         /* User is moving for Black */
6464         if (WhiteOnMove(currentMove)) {
6465             if (!appData.premove) {
6466                 DisplayMoveError(_("It is White's turn"));
6467             } else if (toX >= 0 && toY >= 0) {
6468                 premoveToX = toX;
6469                 premoveToY = toY;
6470                 premoveFromX = fromX;
6471                 premoveFromY = fromY;
6472                 premovePromoChar = promoChar;
6473                 gotPremove = 1;
6474                 if (appData.debugMode)
6475                     fprintf(debugFP, "Got premove: fromX %d,"
6476                             "fromY %d, toX %d, toY %d\n",
6477                             fromX, fromY, toX, toY);
6478             }
6479             return;
6480         }
6481         break;
6482
6483       case IcsPlayingWhite:
6484         /* User is moving for White */
6485         if (!WhiteOnMove(currentMove)) {
6486             if (!appData.premove) {
6487                 DisplayMoveError(_("It is Black's turn"));
6488             } else if (toX >= 0 && toY >= 0) {
6489                 premoveToX = toX;
6490                 premoveToY = toY;
6491                 premoveFromX = fromX;
6492                 premoveFromY = fromY;
6493                 premovePromoChar = promoChar;
6494                 gotPremove = 1;
6495                 if (appData.debugMode)
6496                     fprintf(debugFP, "Got premove: fromX %d,"
6497                             "fromY %d, toX %d, toY %d\n",
6498                             fromX, fromY, toX, toY);
6499             }
6500             return;
6501         }
6502         break;
6503
6504       default:
6505         break;
6506
6507       case EditPosition:
6508         /* EditPosition, empty square, or different color piece;
6509            click-click move is possible */
6510         if (toX == -2 || toY == -2) {
6511             boards[0][fromY][fromX] = EmptySquare;
6512             DrawPosition(FALSE, boards[currentMove]);
6513             return;
6514         } else if (toX >= 0 && toY >= 0) {
6515             boards[0][toY][toX] = boards[0][fromY][fromX];
6516             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6517                 if(boards[0][fromY][0] != EmptySquare) {
6518                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6519                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6520                 }
6521             } else
6522             if(fromX == BOARD_RGHT+1) {
6523                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6524                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6525                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6526                 }
6527             } else
6528             boards[0][fromY][fromX] = EmptySquare;
6529             DrawPosition(FALSE, boards[currentMove]);
6530             return;
6531         }
6532         return;
6533     }
6534
6535     if(toX < 0 || toY < 0) return;
6536     pdown = boards[currentMove][fromY][fromX];
6537     pup = boards[currentMove][toY][toX];
6538
6539     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6540     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6541          if( pup != EmptySquare ) return;
6542          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6543            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6544                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6545            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6546            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6547            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6548            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6549          fromY = DROP_RANK;
6550     }
6551
6552     /* [HGM] always test for legality, to get promotion info */
6553     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6554                                          fromY, fromX, toY, toX, promoChar);
6555
6556     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6557
6558     /* [HGM] but possibly ignore an IllegalMove result */
6559     if (appData.testLegality) {
6560         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6561             DisplayMoveError(_("Illegal move"));
6562             return;
6563         }
6564     }
6565
6566     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6567 }
6568
6569 /* Common tail of UserMoveEvent and DropMenuEvent */
6570 int
6571 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6572 {
6573     char *bookHit = 0;
6574
6575     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6576         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6577         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6578         if(WhiteOnMove(currentMove)) {
6579             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6580         } else {
6581             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6582         }
6583     }
6584
6585     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6586        move type in caller when we know the move is a legal promotion */
6587     if(moveType == NormalMove && promoChar)
6588         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6589
6590     /* [HGM] <popupFix> The following if has been moved here from
6591        UserMoveEvent(). Because it seemed to belong here (why not allow
6592        piece drops in training games?), and because it can only be
6593        performed after it is known to what we promote. */
6594     if (gameMode == Training) {
6595       /* compare the move played on the board to the next move in the
6596        * game. If they match, display the move and the opponent's response.
6597        * If they don't match, display an error message.
6598        */
6599       int saveAnimate;
6600       Board testBoard;
6601       CopyBoard(testBoard, boards[currentMove]);
6602       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6603
6604       if (CompareBoards(testBoard, boards[currentMove+1])) {
6605         ForwardInner(currentMove+1);
6606
6607         /* Autoplay the opponent's response.
6608          * if appData.animate was TRUE when Training mode was entered,
6609          * the response will be animated.
6610          */
6611         saveAnimate = appData.animate;
6612         appData.animate = animateTraining;
6613         ForwardInner(currentMove+1);
6614         appData.animate = saveAnimate;
6615
6616         /* check for the end of the game */
6617         if (currentMove >= forwardMostMove) {
6618           gameMode = PlayFromGameFile;
6619           ModeHighlight();
6620           SetTrainingModeOff();
6621           DisplayInformation(_("End of game"));
6622         }
6623       } else {
6624         DisplayError(_("Incorrect move"), 0);
6625       }
6626       return 1;
6627     }
6628
6629   /* Ok, now we know that the move is good, so we can kill
6630      the previous line in Analysis Mode */
6631   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6632                                 && currentMove < forwardMostMove) {
6633     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6634     else forwardMostMove = currentMove;
6635   }
6636
6637   /* If we need the chess program but it's dead, restart it */
6638   ResurrectChessProgram();
6639
6640   /* A user move restarts a paused game*/
6641   if (pausing)
6642     PauseEvent();
6643
6644   thinkOutput[0] = NULLCHAR;
6645
6646   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6647
6648   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6649     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6650     return 1;
6651   }
6652
6653   if (gameMode == BeginningOfGame) {
6654     if (appData.noChessProgram) {
6655       gameMode = EditGame;
6656       SetGameInfo();
6657     } else {
6658       char buf[MSG_SIZ];
6659       gameMode = MachinePlaysBlack;
6660       StartClocks();
6661       SetGameInfo();
6662       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6663       DisplayTitle(buf);
6664       if (first.sendName) {
6665         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6666         SendToProgram(buf, &first);
6667       }
6668       StartClocks();
6669     }
6670     ModeHighlight();
6671   }
6672
6673   /* Relay move to ICS or chess engine */
6674   if (appData.icsActive) {
6675     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6676         gameMode == IcsExamining) {
6677       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6678         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6679         SendToICS("draw ");
6680         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6681       }
6682       // also send plain move, in case ICS does not understand atomic claims
6683       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6684       ics_user_moved = 1;
6685     }
6686   } else {
6687     if (first.sendTime && (gameMode == BeginningOfGame ||
6688                            gameMode == MachinePlaysWhite ||
6689                            gameMode == MachinePlaysBlack)) {
6690       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6691     }
6692     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6693          // [HGM] book: if program might be playing, let it use book
6694         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6695         first.maybeThinking = TRUE;
6696     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6697         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6698         SendBoard(&first, currentMove+1);
6699     } else SendMoveToProgram(forwardMostMove-1, &first);
6700     if (currentMove == cmailOldMove + 1) {
6701       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6702     }
6703   }
6704
6705   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6706
6707   switch (gameMode) {
6708   case EditGame:
6709     if(appData.testLegality)
6710     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6711     case MT_NONE:
6712     case MT_CHECK:
6713       break;
6714     case MT_CHECKMATE:
6715     case MT_STAINMATE:
6716       if (WhiteOnMove(currentMove)) {
6717         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6718       } else {
6719         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6720       }
6721       break;
6722     case MT_STALEMATE:
6723       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6724       break;
6725     }
6726     break;
6727
6728   case MachinePlaysBlack:
6729   case MachinePlaysWhite:
6730     /* disable certain menu options while machine is thinking */
6731     SetMachineThinkingEnables();
6732     break;
6733
6734   default:
6735     break;
6736   }
6737
6738   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6739   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6740
6741   if(bookHit) { // [HGM] book: simulate book reply
6742         static char bookMove[MSG_SIZ]; // a bit generous?
6743
6744         programStats.nodes = programStats.depth = programStats.time =
6745         programStats.score = programStats.got_only_move = 0;
6746         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6747
6748         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6749         strcat(bookMove, bookHit);
6750         HandleMachineMove(bookMove, &first);
6751   }
6752   return 1;
6753 }
6754
6755 void
6756 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6757 {
6758     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6759     Markers *m = (Markers *) closure;
6760     if(rf == fromY && ff == fromX)
6761         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6762                          || kind == WhiteCapturesEnPassant
6763                          || kind == BlackCapturesEnPassant);
6764     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6765 }
6766
6767 void
6768 MarkTargetSquares (int clear)
6769 {
6770   int x, y;
6771   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6772      !appData.testLegality || gameMode == EditPosition) return;
6773   if(clear) {
6774     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6775   } else {
6776     int capt = 0;
6777     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6778     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6779       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6780       if(capt)
6781       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6782     }
6783   }
6784   DrawPosition(TRUE, NULL);
6785 }
6786
6787 int
6788 Explode (Board board, int fromX, int fromY, int toX, int toY)
6789 {
6790     if(gameInfo.variant == VariantAtomic &&
6791        (board[toY][toX] != EmptySquare ||                     // capture?
6792         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6793                          board[fromY][fromX] == BlackPawn   )
6794       )) {
6795         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6796         return TRUE;
6797     }
6798     return FALSE;
6799 }
6800
6801 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6802
6803 int
6804 CanPromote (ChessSquare piece, int y)
6805 {
6806         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6807         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6808         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6809            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6810            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6811                                                   gameInfo.variant == VariantMakruk) return FALSE;
6812         return (piece == BlackPawn && y == 1 ||
6813                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6814                 piece == BlackLance && y == 1 ||
6815                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6816 }
6817
6818 void
6819 LeftClick (ClickType clickType, int xPix, int yPix)
6820 {
6821     int x, y;
6822     Boolean saveAnimate;
6823     static int second = 0, promotionChoice = 0, clearFlag = 0;
6824     char promoChoice = NULLCHAR;
6825     ChessSquare piece;
6826
6827     if(appData.seekGraph && appData.icsActive && loggedOn &&
6828         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6829         SeekGraphClick(clickType, xPix, yPix, 0);
6830         return;
6831     }
6832
6833     if (clickType == Press) ErrorPopDown();
6834
6835     x = EventToSquare(xPix, BOARD_WIDTH);
6836     y = EventToSquare(yPix, BOARD_HEIGHT);
6837     if (!flipView && y >= 0) {
6838         y = BOARD_HEIGHT - 1 - y;
6839     }
6840     if (flipView && x >= 0) {
6841         x = BOARD_WIDTH - 1 - x;
6842     }
6843
6844     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6845         defaultPromoChoice = promoSweep;
6846         promoSweep = EmptySquare;   // terminate sweep
6847         promoDefaultAltered = TRUE;
6848         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6849     }
6850
6851     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6852         if(clickType == Release) return; // ignore upclick of click-click destination
6853         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6854         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6855         if(gameInfo.holdingsWidth &&
6856                 (WhiteOnMove(currentMove)
6857                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6858                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6859             // click in right holdings, for determining promotion piece
6860             ChessSquare p = boards[currentMove][y][x];
6861             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6862             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6863             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6864                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6865                 fromX = fromY = -1;
6866                 return;
6867             }
6868         }
6869         DrawPosition(FALSE, boards[currentMove]);
6870         return;
6871     }
6872
6873     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6874     if(clickType == Press
6875             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6876               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6877               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6878         return;
6879
6880     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6881         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6882
6883     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6884         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6885                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6886         defaultPromoChoice = DefaultPromoChoice(side);
6887     }
6888
6889     autoQueen = appData.alwaysPromoteToQueen;
6890
6891     if (fromX == -1) {
6892       int originalY = y;
6893       gatingPiece = EmptySquare;
6894       if (clickType != Press) {
6895         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6896             DragPieceEnd(xPix, yPix); dragging = 0;
6897             DrawPosition(FALSE, NULL);
6898         }
6899         return;
6900       }
6901       fromX = x; fromY = y; toX = toY = -1;
6902       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6903          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6904          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6905             /* First square */
6906             if (OKToStartUserMove(fromX, fromY)) {
6907                 second = 0;
6908                 MarkTargetSquares(0);
6909                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6910                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6911                     promoSweep = defaultPromoChoice;
6912                     selectFlag = 0; lastX = xPix; lastY = yPix;
6913                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6914                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6915                 }
6916                 if (appData.highlightDragging) {
6917                     SetHighlights(fromX, fromY, -1, -1);
6918                 }
6919             } else fromX = fromY = -1;
6920             return;
6921         }
6922     }
6923
6924     /* fromX != -1 */
6925     if (clickType == Press && gameMode != EditPosition) {
6926         ChessSquare fromP;
6927         ChessSquare toP;
6928         int frc;
6929
6930         // ignore off-board to clicks
6931         if(y < 0 || x < 0) return;
6932
6933         /* Check if clicking again on the same color piece */
6934         fromP = boards[currentMove][fromY][fromX];
6935         toP = boards[currentMove][y][x];
6936         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6937         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6938              WhitePawn <= toP && toP <= WhiteKing &&
6939              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6940              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6941             (BlackPawn <= fromP && fromP <= BlackKing &&
6942              BlackPawn <= toP && toP <= BlackKing &&
6943              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6944              !(fromP == BlackKing && toP == BlackRook && frc))) {
6945             /* Clicked again on same color piece -- changed his mind */
6946             second = (x == fromX && y == fromY);
6947             promoDefaultAltered = FALSE;
6948             MarkTargetSquares(1);
6949            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6950             if (appData.highlightDragging) {
6951                 SetHighlights(x, y, -1, -1);
6952             } else {
6953                 ClearHighlights();
6954             }
6955             if (OKToStartUserMove(x, y)) {
6956                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6957                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6958                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6959                  gatingPiece = boards[currentMove][fromY][fromX];
6960                 else gatingPiece = EmptySquare;
6961                 fromX = x;
6962                 fromY = y; dragging = 1;
6963                 MarkTargetSquares(0);
6964                 DragPieceBegin(xPix, yPix, FALSE);
6965                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6966                     promoSweep = defaultPromoChoice;
6967                     selectFlag = 0; lastX = xPix; lastY = yPix;
6968                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6969                 }
6970             }
6971            }
6972            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6973            second = FALSE; 
6974         }
6975         // ignore clicks on holdings
6976         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6977     }
6978
6979     if (clickType == Release && x == fromX && y == fromY) {
6980         DragPieceEnd(xPix, yPix); dragging = 0;
6981         if(clearFlag) {
6982             // a deferred attempt to click-click move an empty square on top of a piece
6983             boards[currentMove][y][x] = EmptySquare;
6984             ClearHighlights();
6985             DrawPosition(FALSE, boards[currentMove]);
6986             fromX = fromY = -1; clearFlag = 0;
6987             return;
6988         }
6989         if (appData.animateDragging) {
6990             /* Undo animation damage if any */
6991             DrawPosition(FALSE, NULL);
6992         }
6993         if (second) {
6994             /* Second up/down in same square; just abort move */
6995             second = 0;
6996             fromX = fromY = -1;
6997             gatingPiece = EmptySquare;
6998             ClearHighlights();
6999             gotPremove = 0;
7000             ClearPremoveHighlights();
7001         } else {
7002             /* First upclick in same square; start click-click mode */
7003             SetHighlights(x, y, -1, -1);
7004         }
7005         return;
7006     }
7007
7008     clearFlag = 0;
7009
7010     /* we now have a different from- and (possibly off-board) to-square */
7011     /* Completed move */
7012     toX = x;
7013     toY = y;
7014     saveAnimate = appData.animate;
7015     MarkTargetSquares(1);
7016     if (clickType == Press) {
7017         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7018             // must be Edit Position mode with empty-square selected
7019             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7020             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7021             return;
7022         }
7023         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7024             ChessSquare piece = boards[currentMove][fromY][fromX];
7025             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7026             promoSweep = defaultPromoChoice;
7027             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7028             selectFlag = 0; lastX = xPix; lastY = yPix;
7029             Sweep(0); // Pawn that is going to promote: preview promotion piece
7030             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7031             DrawPosition(FALSE, boards[currentMove]);
7032             return;
7033         }
7034         /* Finish clickclick move */
7035         if (appData.animate || appData.highlightLastMove) {
7036             SetHighlights(fromX, fromY, toX, toY);
7037         } else {
7038             ClearHighlights();
7039         }
7040     } else {
7041         /* Finish drag move */
7042         if (appData.highlightLastMove) {
7043             SetHighlights(fromX, fromY, toX, toY);
7044         } else {
7045             ClearHighlights();
7046         }
7047         DragPieceEnd(xPix, yPix); dragging = 0;
7048         /* Don't animate move and drag both */
7049         appData.animate = FALSE;
7050     }
7051
7052     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7053     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7054         ChessSquare piece = boards[currentMove][fromY][fromX];
7055         if(gameMode == EditPosition && piece != EmptySquare &&
7056            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7057             int n;
7058
7059             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7060                 n = PieceToNumber(piece - (int)BlackPawn);
7061                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7062                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7063                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7064             } else
7065             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7066                 n = PieceToNumber(piece);
7067                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7068                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7069                 boards[currentMove][n][BOARD_WIDTH-2]++;
7070             }
7071             boards[currentMove][fromY][fromX] = EmptySquare;
7072         }
7073         ClearHighlights();
7074         fromX = fromY = -1;
7075         DrawPosition(TRUE, boards[currentMove]);
7076         return;
7077     }
7078
7079     // off-board moves should not be highlighted
7080     if(x < 0 || y < 0) ClearHighlights();
7081
7082     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7083
7084     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7085         SetHighlights(fromX, fromY, toX, toY);
7086         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7087             // [HGM] super: promotion to captured piece selected from holdings
7088             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7089             promotionChoice = TRUE;
7090             // kludge follows to temporarily execute move on display, without promoting yet
7091             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7092             boards[currentMove][toY][toX] = p;
7093             DrawPosition(FALSE, boards[currentMove]);
7094             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7095             boards[currentMove][toY][toX] = q;
7096             DisplayMessage("Click in holdings to choose piece", "");
7097             return;
7098         }
7099         PromotionPopUp();
7100     } else {
7101         int oldMove = currentMove;
7102         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7103         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7104         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7105         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7106            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7107             DrawPosition(TRUE, boards[currentMove]);
7108         fromX = fromY = -1;
7109     }
7110     appData.animate = saveAnimate;
7111     if (appData.animate || appData.animateDragging) {
7112         /* Undo animation damage if needed */
7113         DrawPosition(FALSE, NULL);
7114     }
7115 }
7116
7117 int
7118 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7119 {   // front-end-free part taken out of PieceMenuPopup
7120     int whichMenu; int xSqr, ySqr;
7121
7122     if(seekGraphUp) { // [HGM] seekgraph
7123         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7124         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7125         return -2;
7126     }
7127
7128     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7129          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7130         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7131         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7132         if(action == Press)   {
7133             originalFlip = flipView;
7134             flipView = !flipView; // temporarily flip board to see game from partners perspective
7135             DrawPosition(TRUE, partnerBoard);
7136             DisplayMessage(partnerStatus, "");
7137             partnerUp = TRUE;
7138         } else if(action == Release) {
7139             flipView = originalFlip;
7140             DrawPosition(TRUE, boards[currentMove]);
7141             partnerUp = FALSE;
7142         }
7143         return -2;
7144     }
7145
7146     xSqr = EventToSquare(x, BOARD_WIDTH);
7147     ySqr = EventToSquare(y, BOARD_HEIGHT);
7148     if (action == Release) {
7149         if(pieceSweep != EmptySquare) {
7150             EditPositionMenuEvent(pieceSweep, toX, toY);
7151             pieceSweep = EmptySquare;
7152         } else UnLoadPV(); // [HGM] pv
7153     }
7154     if (action != Press) return -2; // return code to be ignored
7155     switch (gameMode) {
7156       case IcsExamining:
7157         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7158       case EditPosition:
7159         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7160         if (xSqr < 0 || ySqr < 0) return -1;
7161         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7162         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7163         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7164         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7165         NextPiece(0);
7166         return 2; // grab
7167       case IcsObserving:
7168         if(!appData.icsEngineAnalyze) return -1;
7169       case IcsPlayingWhite:
7170       case IcsPlayingBlack:
7171         if(!appData.zippyPlay) goto noZip;
7172       case AnalyzeMode:
7173       case AnalyzeFile:
7174       case MachinePlaysWhite:
7175       case MachinePlaysBlack:
7176       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7177         if (!appData.dropMenu) {
7178           LoadPV(x, y);
7179           return 2; // flag front-end to grab mouse events
7180         }
7181         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7182            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7183       case EditGame:
7184       noZip:
7185         if (xSqr < 0 || ySqr < 0) return -1;
7186         if (!appData.dropMenu || appData.testLegality &&
7187             gameInfo.variant != VariantBughouse &&
7188             gameInfo.variant != VariantCrazyhouse) return -1;
7189         whichMenu = 1; // drop menu
7190         break;
7191       default:
7192         return -1;
7193     }
7194
7195     if (((*fromX = xSqr) < 0) ||
7196         ((*fromY = ySqr) < 0)) {
7197         *fromX = *fromY = -1;
7198         return -1;
7199     }
7200     if (flipView)
7201       *fromX = BOARD_WIDTH - 1 - *fromX;
7202     else
7203       *fromY = BOARD_HEIGHT - 1 - *fromY;
7204
7205     return whichMenu;
7206 }
7207
7208 void
7209 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7210 {
7211 //    char * hint = lastHint;
7212     FrontEndProgramStats stats;
7213
7214     stats.which = cps == &first ? 0 : 1;
7215     stats.depth = cpstats->depth;
7216     stats.nodes = cpstats->nodes;
7217     stats.score = cpstats->score;
7218     stats.time = cpstats->time;
7219     stats.pv = cpstats->movelist;
7220     stats.hint = lastHint;
7221     stats.an_move_index = 0;
7222     stats.an_move_count = 0;
7223
7224     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7225         stats.hint = cpstats->move_name;
7226         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7227         stats.an_move_count = cpstats->nr_moves;
7228     }
7229
7230     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
7231
7232     SetProgramStats( &stats );
7233 }
7234
7235 void
7236 ClearEngineOutputPane (int which)
7237 {
7238     static FrontEndProgramStats dummyStats;
7239     dummyStats.which = which;
7240     dummyStats.pv = "#";
7241     SetProgramStats( &dummyStats );
7242 }
7243
7244 #define MAXPLAYERS 500
7245
7246 char *
7247 TourneyStandings (int display)
7248 {
7249     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7250     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7251     char result, *p, *names[MAXPLAYERS];
7252
7253     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7254         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7255     names[0] = p = strdup(appData.participants);
7256     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7257
7258     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7259
7260     while(result = appData.results[nr]) {
7261         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7262         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7263         wScore = bScore = 0;
7264         switch(result) {
7265           case '+': wScore = 2; break;
7266           case '-': bScore = 2; break;
7267           case '=': wScore = bScore = 1; break;
7268           case ' ':
7269           case '*': return strdup("busy"); // tourney not finished
7270         }
7271         score[w] += wScore;
7272         score[b] += bScore;
7273         games[w]++;
7274         games[b]++;
7275         nr++;
7276     }
7277     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7278     for(w=0; w<nPlayers; w++) {
7279         bScore = -1;
7280         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7281         ranking[w] = b; points[w] = bScore; score[b] = -2;
7282     }
7283     p = malloc(nPlayers*34+1);
7284     for(w=0; w<nPlayers && w<display; w++)
7285         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7286     free(names[0]);
7287     return p;
7288 }
7289
7290 void
7291 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7292 {       // count all piece types
7293         int p, f, r;
7294         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7295         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7296         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7297                 p = board[r][f];
7298                 pCnt[p]++;
7299                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7300                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7301                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7302                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7303                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7304                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7305         }
7306 }
7307
7308 int
7309 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7310 {
7311         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7312         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7313
7314         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7315         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7316         if(myPawns == 2 && nMine == 3) // KPP
7317             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7318         if(myPawns == 1 && nMine == 2) // KP
7319             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7320         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7321             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7322         if(myPawns) return FALSE;
7323         if(pCnt[WhiteRook+side])
7324             return pCnt[BlackRook-side] ||
7325                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7326                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7327                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7328         if(pCnt[WhiteCannon+side]) {
7329             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7330             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7331         }
7332         if(pCnt[WhiteKnight+side])
7333             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7334         return FALSE;
7335 }
7336
7337 int
7338 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7339 {
7340         VariantClass v = gameInfo.variant;
7341
7342         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7343         if(v == VariantShatranj) return TRUE; // always winnable through baring
7344         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7345         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7346
7347         if(v == VariantXiangqi) {
7348                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7349
7350                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7351                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7352                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7353                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7354                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7355                 if(stale) // we have at least one last-rank P plus perhaps C
7356                     return majors // KPKX
7357                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7358                 else // KCA*E*
7359                     return pCnt[WhiteFerz+side] // KCAK
7360                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7361                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7362                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7363
7364         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7365                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7366
7367                 if(nMine == 1) return FALSE; // bare King
7368                 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
7369                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7370                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7371                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7372                 if(pCnt[WhiteKnight+side])
7373                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7374                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7375                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7376                 if(nBishops)
7377                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7378                 if(pCnt[WhiteAlfil+side])
7379                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7380                 if(pCnt[WhiteWazir+side])
7381                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7382         }
7383
7384         return TRUE;
7385 }
7386
7387 int
7388 CompareWithRights (Board b1, Board b2)
7389 {
7390     int rights = 0;
7391     if(!CompareBoards(b1, b2)) return FALSE;
7392     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7393     /* compare castling rights */
7394     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7395            rights++; /* King lost rights, while rook still had them */
7396     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7397         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7398            rights++; /* but at least one rook lost them */
7399     }
7400     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7401            rights++;
7402     if( b1[CASTLING][5] != NoRights ) {
7403         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7404            rights++;
7405     }
7406     return rights == 0;
7407 }
7408
7409 int
7410 Adjudicate (ChessProgramState *cps)
7411 {       // [HGM] some adjudications useful with buggy engines
7412         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7413         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7414         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7415         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7416         int k, count = 0; static int bare = 1;
7417         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7418         Boolean canAdjudicate = !appData.icsActive;
7419
7420         // most tests only when we understand the game, i.e. legality-checking on
7421             if( appData.testLegality )
7422             {   /* [HGM] Some more adjudications for obstinate engines */
7423                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7424                 static int moveCount = 6;
7425                 ChessMove result;
7426                 char *reason = NULL;
7427
7428                 /* Count what is on board. */
7429                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7430
7431                 /* Some material-based adjudications that have to be made before stalemate test */
7432                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7433                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7434                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7435                      if(canAdjudicate && appData.checkMates) {
7436                          if(engineOpponent)
7437                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7438                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7439                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7440                          return 1;
7441                      }
7442                 }
7443
7444                 /* Bare King in Shatranj (loses) or Losers (wins) */
7445                 if( nrW == 1 || nrB == 1) {
7446                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7447                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7448                      if(canAdjudicate && appData.checkMates) {
7449                          if(engineOpponent)
7450                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7451                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7452                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7453                          return 1;
7454                      }
7455                   } else
7456                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7457                   {    /* bare King */
7458                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7459                         if(canAdjudicate && appData.checkMates) {
7460                             /* but only adjudicate if adjudication enabled */
7461                             if(engineOpponent)
7462                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7463                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7464                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7465                             return 1;
7466                         }
7467                   }
7468                 } else bare = 1;
7469
7470
7471             // don't wait for engine to announce game end if we can judge ourselves
7472             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7473               case MT_CHECK:
7474                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7475                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7476                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7477                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7478                             checkCnt++;
7479                         if(checkCnt >= 2) {
7480                             reason = "Xboard adjudication: 3rd check";
7481                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7482                             break;
7483                         }
7484                     }
7485                 }
7486               case MT_NONE:
7487               default:
7488                 break;
7489               case MT_STALEMATE:
7490               case MT_STAINMATE:
7491                 reason = "Xboard adjudication: Stalemate";
7492                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7493                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7494                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7495                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7496                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7497                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7498                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7499                                                                         EP_CHECKMATE : EP_WINS);
7500                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7501                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7502                 }
7503                 break;
7504               case MT_CHECKMATE:
7505                 reason = "Xboard adjudication: Checkmate";
7506                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7507                 break;
7508             }
7509
7510                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7511                     case EP_STALEMATE:
7512                         result = GameIsDrawn; break;
7513                     case EP_CHECKMATE:
7514                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7515                     case EP_WINS:
7516                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7517                     default:
7518                         result = EndOfFile;
7519                 }
7520                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7521                     if(engineOpponent)
7522                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7523                     GameEnds( result, reason, GE_XBOARD );
7524                     return 1;
7525                 }
7526
7527                 /* Next absolutely insufficient mating material. */
7528                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7529                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7530                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7531
7532                      /* always flag draws, for judging claims */
7533                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7534
7535                      if(canAdjudicate && appData.materialDraws) {
7536                          /* but only adjudicate them if adjudication enabled */
7537                          if(engineOpponent) {
7538                            SendToProgram("force\n", engineOpponent); // suppress reply
7539                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7540                          }
7541                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7542                          return 1;
7543                      }
7544                 }
7545
7546                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7547                 if(gameInfo.variant == VariantXiangqi ?
7548                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7549                  : nrW + nrB == 4 &&
7550                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7551                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7552                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7553                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7554                    ) ) {
7555                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7556                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7557                           if(engineOpponent) {
7558                             SendToProgram("force\n", engineOpponent); // suppress reply
7559                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7560                           }
7561                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7562                           return 1;
7563                      }
7564                 } else moveCount = 6;
7565             }
7566         if (appData.debugMode) { int i;
7567             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7568                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7569                     appData.drawRepeats);
7570             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7571               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7572
7573         }
7574
7575         // Repetition draws and 50-move rule can be applied independently of legality testing
7576
7577                 /* Check for rep-draws */
7578                 count = 0;
7579                 for(k = forwardMostMove-2;
7580                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7581                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7582                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7583                     k-=2)
7584                 {   int rights=0;
7585                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7586                         /* compare castling rights */
7587                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7588                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7589                                 rights++; /* King lost rights, while rook still had them */
7590                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7591                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7592                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7593                                    rights++; /* but at least one rook lost them */
7594                         }
7595                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7596                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7597                                 rights++;
7598                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7599                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7600                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7601                                    rights++;
7602                         }
7603                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7604                             && appData.drawRepeats > 1) {
7605                              /* adjudicate after user-specified nr of repeats */
7606                              int result = GameIsDrawn;
7607                              char *details = "XBoard adjudication: repetition draw";
7608                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7609                                 // [HGM] xiangqi: check for forbidden perpetuals
7610                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7611                                 for(m=forwardMostMove; m>k; m-=2) {
7612                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7613                                         ourPerpetual = 0; // the current mover did not always check
7614                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7615                                         hisPerpetual = 0; // the opponent did not always check
7616                                 }
7617                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7618                                                                         ourPerpetual, hisPerpetual);
7619                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7620                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7621                                     details = "Xboard adjudication: perpetual checking";
7622                                 } else
7623                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7624                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7625                                 } else
7626                                 // Now check for perpetual chases
7627                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7628                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7629                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7630                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7631                                         static char resdet[MSG_SIZ];
7632                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7633                                         details = resdet;
7634                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7635                                     } else
7636                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7637                                         break; // Abort repetition-checking loop.
7638                                 }
7639                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7640                              }
7641                              if(engineOpponent) {
7642                                SendToProgram("force\n", engineOpponent); // suppress reply
7643                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7644                              }
7645                              GameEnds( result, details, GE_XBOARD );
7646                              return 1;
7647                         }
7648                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7649                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7650                     }
7651                 }
7652
7653                 /* Now we test for 50-move draws. Determine ply count */
7654                 count = forwardMostMove;
7655                 /* look for last irreversble move */
7656                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7657                     count--;
7658                 /* if we hit starting position, add initial plies */
7659                 if( count == backwardMostMove )
7660                     count -= initialRulePlies;
7661                 count = forwardMostMove - count;
7662                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7663                         // adjust reversible move counter for checks in Xiangqi
7664                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7665                         if(i < backwardMostMove) i = backwardMostMove;
7666                         while(i <= forwardMostMove) {
7667                                 lastCheck = inCheck; // check evasion does not count
7668                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7669                                 if(inCheck || lastCheck) count--; // check does not count
7670                                 i++;
7671                         }
7672                 }
7673                 if( count >= 100)
7674                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7675                          /* this is used to judge if draw claims are legal */
7676                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7677                          if(engineOpponent) {
7678                            SendToProgram("force\n", engineOpponent); // suppress reply
7679                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7680                          }
7681                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7682                          return 1;
7683                 }
7684
7685                 /* if draw offer is pending, treat it as a draw claim
7686                  * when draw condition present, to allow engines a way to
7687                  * claim draws before making their move to avoid a race
7688                  * condition occurring after their move
7689                  */
7690                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7691                          char *p = NULL;
7692                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7693                              p = "Draw claim: 50-move rule";
7694                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7695                              p = "Draw claim: 3-fold repetition";
7696                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7697                              p = "Draw claim: insufficient mating material";
7698                          if( p != NULL && canAdjudicate) {
7699                              if(engineOpponent) {
7700                                SendToProgram("force\n", engineOpponent); // suppress reply
7701                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7702                              }
7703                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7704                              return 1;
7705                          }
7706                 }
7707
7708                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7709                     if(engineOpponent) {
7710                       SendToProgram("force\n", engineOpponent); // suppress reply
7711                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7712                     }
7713                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7714                     return 1;
7715                 }
7716         return 0;
7717 }
7718
7719 char *
7720 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7721 {   // [HGM] book: this routine intercepts moves to simulate book replies
7722     char *bookHit = NULL;
7723
7724     //first determine if the incoming move brings opponent into his book
7725     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7726         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7727     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7728     if(bookHit != NULL && !cps->bookSuspend) {
7729         // make sure opponent is not going to reply after receiving move to book position
7730         SendToProgram("force\n", cps);
7731         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7732     }
7733     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7734     // now arrange restart after book miss
7735     if(bookHit) {
7736         // after a book hit we never send 'go', and the code after the call to this routine
7737         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7738         char buf[MSG_SIZ], *move = bookHit;
7739         if(cps->useSAN) {
7740             int fromX, fromY, toX, toY;
7741             char promoChar;
7742             ChessMove moveType;
7743             move = buf + 30;
7744             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7745                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7746                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7747                                     PosFlags(forwardMostMove),
7748                                     fromY, fromX, toY, toX, promoChar, move);
7749             } else {
7750                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7751                 bookHit = NULL;
7752             }
7753         }
7754         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7755         SendToProgram(buf, cps);
7756         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7757     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7758         SendToProgram("go\n", cps);
7759         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7760     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7761         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7762             SendToProgram("go\n", cps);
7763         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7764     }
7765     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7766 }
7767
7768 char *savedMessage;
7769 ChessProgramState *savedState;
7770 void
7771 DeferredBookMove (void)
7772 {
7773         if(savedState->lastPing != savedState->lastPong)
7774                     ScheduleDelayedEvent(DeferredBookMove, 10);
7775         else
7776         HandleMachineMove(savedMessage, savedState);
7777 }
7778
7779 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7780
7781 void
7782 HandleMachineMove (char *message, ChessProgramState *cps)
7783 {
7784     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7785     char realname[MSG_SIZ];
7786     int fromX, fromY, toX, toY;
7787     ChessMove moveType;
7788     char promoChar;
7789     char *p, *pv=buf1;
7790     int machineWhite;
7791     char *bookHit;
7792
7793     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7794         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7795         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7796             DisplayError(_("Invalid pairing from pairing engine"), 0);
7797             return;
7798         }
7799         pairingReceived = 1;
7800         NextMatchGame();
7801         return; // Skim the pairing messages here.
7802     }
7803
7804     cps->userError = 0;
7805
7806 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7807     /*
7808      * Kludge to ignore BEL characters
7809      */
7810     while (*message == '\007') message++;
7811
7812     /*
7813      * [HGM] engine debug message: ignore lines starting with '#' character
7814      */
7815     if(cps->debug && *message == '#') return;
7816
7817     /*
7818      * Look for book output
7819      */
7820     if (cps == &first && bookRequested) {
7821         if (message[0] == '\t' || message[0] == ' ') {
7822             /* Part of the book output is here; append it */
7823             strcat(bookOutput, message);
7824             strcat(bookOutput, "  \n");
7825             return;
7826         } else if (bookOutput[0] != NULLCHAR) {
7827             /* All of book output has arrived; display it */
7828             char *p = bookOutput;
7829             while (*p != NULLCHAR) {
7830                 if (*p == '\t') *p = ' ';
7831                 p++;
7832             }
7833             DisplayInformation(bookOutput);
7834             bookRequested = FALSE;
7835             /* Fall through to parse the current output */
7836         }
7837     }
7838
7839     /*
7840      * Look for machine move.
7841      */
7842     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7843         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7844     {
7845         /* This method is only useful on engines that support ping */
7846         if (cps->lastPing != cps->lastPong) {
7847           if (gameMode == BeginningOfGame) {
7848             /* Extra move from before last new; ignore */
7849             if (appData.debugMode) {
7850                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7851             }
7852           } else {
7853             if (appData.debugMode) {
7854                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7855                         cps->which, gameMode);
7856             }
7857
7858             SendToProgram("undo\n", cps);
7859           }
7860           return;
7861         }
7862
7863         switch (gameMode) {
7864           case BeginningOfGame:
7865             /* Extra move from before last reset; ignore */
7866             if (appData.debugMode) {
7867                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7868             }
7869             return;
7870
7871           case EndOfGame:
7872           case IcsIdle:
7873           default:
7874             /* Extra move after we tried to stop.  The mode test is
7875                not a reliable way of detecting this problem, but it's
7876                the best we can do on engines that don't support ping.
7877             */
7878             if (appData.debugMode) {
7879                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7880                         cps->which, gameMode);
7881             }
7882             SendToProgram("undo\n", cps);
7883             return;
7884
7885           case MachinePlaysWhite:
7886           case IcsPlayingWhite:
7887             machineWhite = TRUE;
7888             break;
7889
7890           case MachinePlaysBlack:
7891           case IcsPlayingBlack:
7892             machineWhite = FALSE;
7893             break;
7894
7895           case TwoMachinesPlay:
7896             machineWhite = (cps->twoMachinesColor[0] == 'w');
7897             break;
7898         }
7899         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7900             if (appData.debugMode) {
7901                 fprintf(debugFP,
7902                         "Ignoring move out of turn by %s, gameMode %d"
7903                         ", forwardMost %d\n",
7904                         cps->which, gameMode, forwardMostMove);
7905             }
7906             return;
7907         }
7908
7909     if (appData.debugMode) { int f = forwardMostMove;
7910         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7911                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7912                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7913     }
7914         if(cps->alphaRank) AlphaRank(machineMove, 4);
7915         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7916                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7917             /* Machine move could not be parsed; ignore it. */
7918           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7919                     machineMove, _(cps->which));
7920             DisplayError(buf1, 0);
7921             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7922                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7923             if (gameMode == TwoMachinesPlay) {
7924               GameEnds(machineWhite ? BlackWins : WhiteWins,
7925                        buf1, GE_XBOARD);
7926             }
7927             return;
7928         }
7929
7930         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7931         /* So we have to redo legality test with true e.p. status here,  */
7932         /* to make sure an illegal e.p. capture does not slip through,   */
7933         /* to cause a forfeit on a justified illegal-move complaint      */
7934         /* of the opponent.                                              */
7935         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7936            ChessMove moveType;
7937            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7938                              fromY, fromX, toY, toX, promoChar);
7939             if (appData.debugMode) {
7940                 int i;
7941                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7942                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7943                 fprintf(debugFP, "castling rights\n");
7944             }
7945             if(moveType == IllegalMove) {
7946               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7947                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7948                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7949                            buf1, GE_XBOARD);
7950                 return;
7951            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7952            /* [HGM] Kludge to handle engines that send FRC-style castling
7953               when they shouldn't (like TSCP-Gothic) */
7954            switch(moveType) {
7955              case WhiteASideCastleFR:
7956              case BlackASideCastleFR:
7957                toX+=2;
7958                currentMoveString[2]++;
7959                break;
7960              case WhiteHSideCastleFR:
7961              case BlackHSideCastleFR:
7962                toX--;
7963                currentMoveString[2]--;
7964                break;
7965              default: ; // nothing to do, but suppresses warning of pedantic compilers
7966            }
7967         }
7968         hintRequested = FALSE;
7969         lastHint[0] = NULLCHAR;
7970         bookRequested = FALSE;
7971         /* Program may be pondering now */
7972         cps->maybeThinking = TRUE;
7973         if (cps->sendTime == 2) cps->sendTime = 1;
7974         if (cps->offeredDraw) cps->offeredDraw--;
7975
7976         /* [AS] Save move info*/
7977         pvInfoList[ forwardMostMove ].score = programStats.score;
7978         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7979         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7980
7981         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7982
7983         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7984         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7985             int count = 0;
7986
7987             while( count < adjudicateLossPlies ) {
7988                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7989
7990                 if( count & 1 ) {
7991                     score = -score; /* Flip score for winning side */
7992                 }
7993
7994                 if( score > adjudicateLossThreshold ) {
7995                     break;
7996                 }
7997
7998                 count++;
7999             }
8000
8001             if( count >= adjudicateLossPlies ) {
8002                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8003
8004                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8005                     "Xboard adjudication",
8006                     GE_XBOARD );
8007
8008                 return;
8009             }
8010         }
8011
8012         if(Adjudicate(cps)) {
8013             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8014             return; // [HGM] adjudicate: for all automatic game ends
8015         }
8016
8017 #if ZIPPY
8018         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8019             first.initDone) {
8020           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8021                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8022                 SendToICS("draw ");
8023                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8024           }
8025           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8026           ics_user_moved = 1;
8027           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8028                 char buf[3*MSG_SIZ];
8029
8030                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8031                         programStats.score / 100.,
8032                         programStats.depth,
8033                         programStats.time / 100.,
8034                         (unsigned int)programStats.nodes,
8035                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8036                         programStats.movelist);
8037                 SendToICS(buf);
8038 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8039           }
8040         }
8041 #endif
8042
8043         /* [AS] Clear stats for next move */
8044         ClearProgramStats();
8045         thinkOutput[0] = NULLCHAR;
8046         hiddenThinkOutputState = 0;
8047
8048         bookHit = NULL;
8049         if (gameMode == TwoMachinesPlay) {
8050             /* [HGM] relaying draw offers moved to after reception of move */
8051             /* and interpreting offer as claim if it brings draw condition */
8052             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8053                 SendToProgram("draw\n", cps->other);
8054             }
8055             if (cps->other->sendTime) {
8056                 SendTimeRemaining(cps->other,
8057                                   cps->other->twoMachinesColor[0] == 'w');
8058             }
8059             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8060             if (firstMove && !bookHit) {
8061                 firstMove = FALSE;
8062                 if (cps->other->useColors) {
8063                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8064                 }
8065                 SendToProgram("go\n", cps->other);
8066             }
8067             cps->other->maybeThinking = TRUE;
8068         }
8069
8070         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8071
8072         if (!pausing && appData.ringBellAfterMoves) {
8073             RingBell();
8074         }
8075
8076         /*
8077          * Reenable menu items that were disabled while
8078          * machine was thinking
8079          */
8080         if (gameMode != TwoMachinesPlay)
8081             SetUserThinkingEnables();
8082
8083         // [HGM] book: after book hit opponent has received move and is now in force mode
8084         // force the book reply into it, and then fake that it outputted this move by jumping
8085         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8086         if(bookHit) {
8087                 static char bookMove[MSG_SIZ]; // a bit generous?
8088
8089                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8090                 strcat(bookMove, bookHit);
8091                 message = bookMove;
8092                 cps = cps->other;
8093                 programStats.nodes = programStats.depth = programStats.time =
8094                 programStats.score = programStats.got_only_move = 0;
8095                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8096
8097                 if(cps->lastPing != cps->lastPong) {
8098                     savedMessage = message; // args for deferred call
8099                     savedState = cps;
8100                     ScheduleDelayedEvent(DeferredBookMove, 10);
8101                     return;
8102                 }
8103                 goto FakeBookMove;
8104         }
8105
8106         return;
8107     }
8108
8109     /* Set special modes for chess engines.  Later something general
8110      *  could be added here; for now there is just one kludge feature,
8111      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8112      *  when "xboard" is given as an interactive command.
8113      */
8114     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8115         cps->useSigint = FALSE;
8116         cps->useSigterm = FALSE;
8117     }
8118     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8119       ParseFeatures(message+8, cps);
8120       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8121     }
8122
8123     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8124                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8125       int dummy, s=6; char buf[MSG_SIZ];
8126       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8127       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8128       if(startedFromSetupPosition) return;
8129       ParseFEN(boards[0], &dummy, message+s);
8130       DrawPosition(TRUE, boards[0]);
8131       startedFromSetupPosition = TRUE;
8132       return;
8133     }
8134     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8135      * want this, I was asked to put it in, and obliged.
8136      */
8137     if (!strncmp(message, "setboard ", 9)) {
8138         Board initial_position;
8139
8140         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8141
8142         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8143             DisplayError(_("Bad FEN received from engine"), 0);
8144             return ;
8145         } else {
8146            Reset(TRUE, FALSE);
8147            CopyBoard(boards[0], initial_position);
8148            initialRulePlies = FENrulePlies;
8149            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8150            else gameMode = MachinePlaysBlack;
8151            DrawPosition(FALSE, boards[currentMove]);
8152         }
8153         return;
8154     }
8155
8156     /*
8157      * Look for communication commands
8158      */
8159     if (!strncmp(message, "telluser ", 9)) {
8160         if(message[9] == '\\' && message[10] == '\\')
8161             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8162         PlayTellSound();
8163         DisplayNote(message + 9);
8164         return;
8165     }
8166     if (!strncmp(message, "tellusererror ", 14)) {
8167         cps->userError = 1;
8168         if(message[14] == '\\' && message[15] == '\\')
8169             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8170         PlayTellSound();
8171         DisplayError(message + 14, 0);
8172         return;
8173     }
8174     if (!strncmp(message, "tellopponent ", 13)) {
8175       if (appData.icsActive) {
8176         if (loggedOn) {
8177           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8178           SendToICS(buf1);
8179         }
8180       } else {
8181         DisplayNote(message + 13);
8182       }
8183       return;
8184     }
8185     if (!strncmp(message, "tellothers ", 11)) {
8186       if (appData.icsActive) {
8187         if (loggedOn) {
8188           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8189           SendToICS(buf1);
8190         }
8191       }
8192       return;
8193     }
8194     if (!strncmp(message, "tellall ", 8)) {
8195       if (appData.icsActive) {
8196         if (loggedOn) {
8197           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8198           SendToICS(buf1);
8199         }
8200       } else {
8201         DisplayNote(message + 8);
8202       }
8203       return;
8204     }
8205     if (strncmp(message, "warning", 7) == 0) {
8206         /* Undocumented feature, use tellusererror in new code */
8207         DisplayError(message, 0);
8208         return;
8209     }
8210     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8211         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8212         strcat(realname, " query");
8213         AskQuestion(realname, buf2, buf1, cps->pr);
8214         return;
8215     }
8216     /* Commands from the engine directly to ICS.  We don't allow these to be
8217      *  sent until we are logged on. Crafty kibitzes have been known to
8218      *  interfere with the login process.
8219      */
8220     if (loggedOn) {
8221         if (!strncmp(message, "tellics ", 8)) {
8222             SendToICS(message + 8);
8223             SendToICS("\n");
8224             return;
8225         }
8226         if (!strncmp(message, "tellicsnoalias ", 15)) {
8227             SendToICS(ics_prefix);
8228             SendToICS(message + 15);
8229             SendToICS("\n");
8230             return;
8231         }
8232         /* The following are for backward compatibility only */
8233         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8234             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8235             SendToICS(ics_prefix);
8236             SendToICS(message);
8237             SendToICS("\n");
8238             return;
8239         }
8240     }
8241     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8242         return;
8243     }
8244     /*
8245      * If the move is illegal, cancel it and redraw the board.
8246      * Also deal with other error cases.  Matching is rather loose
8247      * here to accommodate engines written before the spec.
8248      */
8249     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8250         strncmp(message, "Error", 5) == 0) {
8251         if (StrStr(message, "name") ||
8252             StrStr(message, "rating") || StrStr(message, "?") ||
8253             StrStr(message, "result") || StrStr(message, "board") ||
8254             StrStr(message, "bk") || StrStr(message, "computer") ||
8255             StrStr(message, "variant") || StrStr(message, "hint") ||
8256             StrStr(message, "random") || StrStr(message, "depth") ||
8257             StrStr(message, "accepted")) {
8258             return;
8259         }
8260         if (StrStr(message, "protover")) {
8261           /* Program is responding to input, so it's apparently done
8262              initializing, and this error message indicates it is
8263              protocol version 1.  So we don't need to wait any longer
8264              for it to initialize and send feature commands. */
8265           FeatureDone(cps, 1);
8266           cps->protocolVersion = 1;
8267           return;
8268         }
8269         cps->maybeThinking = FALSE;
8270
8271         if (StrStr(message, "draw")) {
8272             /* Program doesn't have "draw" command */
8273             cps->sendDrawOffers = 0;
8274             return;
8275         }
8276         if (cps->sendTime != 1 &&
8277             (StrStr(message, "time") || StrStr(message, "otim"))) {
8278           /* Program apparently doesn't have "time" or "otim" command */
8279           cps->sendTime = 0;
8280           return;
8281         }
8282         if (StrStr(message, "analyze")) {
8283             cps->analysisSupport = FALSE;
8284             cps->analyzing = FALSE;
8285 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8286             EditGameEvent(); // [HGM] try to preserve loaded game
8287             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8288             DisplayError(buf2, 0);
8289             return;
8290         }
8291         if (StrStr(message, "(no matching move)st")) {
8292           /* Special kludge for GNU Chess 4 only */
8293           cps->stKludge = TRUE;
8294           SendTimeControl(cps, movesPerSession, timeControl,
8295                           timeIncrement, appData.searchDepth,
8296                           searchTime);
8297           return;
8298         }
8299         if (StrStr(message, "(no matching move)sd")) {
8300           /* Special kludge for GNU Chess 4 only */
8301           cps->sdKludge = TRUE;
8302           SendTimeControl(cps, movesPerSession, timeControl,
8303                           timeIncrement, appData.searchDepth,
8304                           searchTime);
8305           return;
8306         }
8307         if (!StrStr(message, "llegal")) {
8308             return;
8309         }
8310         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8311             gameMode == IcsIdle) return;
8312         if (forwardMostMove <= backwardMostMove) return;
8313         if (pausing) PauseEvent();
8314       if(appData.forceIllegal) {
8315             // [HGM] illegal: machine refused move; force position after move into it
8316           SendToProgram("force\n", cps);
8317           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8318                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8319                 // when black is to move, while there might be nothing on a2 or black
8320                 // might already have the move. So send the board as if white has the move.
8321                 // But first we must change the stm of the engine, as it refused the last move
8322                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8323                 if(WhiteOnMove(forwardMostMove)) {
8324                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8325                     SendBoard(cps, forwardMostMove); // kludgeless board
8326                 } else {
8327                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8328                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8329                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8330                 }
8331           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8332             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8333                  gameMode == TwoMachinesPlay)
8334               SendToProgram("go\n", cps);
8335             return;
8336       } else
8337         if (gameMode == PlayFromGameFile) {
8338             /* Stop reading this game file */
8339             gameMode = EditGame;
8340             ModeHighlight();
8341         }
8342         /* [HGM] illegal-move claim should forfeit game when Xboard */
8343         /* only passes fully legal moves                            */
8344         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8345             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8346                                 "False illegal-move claim", GE_XBOARD );
8347             return; // do not take back move we tested as valid
8348         }
8349         currentMove = forwardMostMove-1;
8350         DisplayMove(currentMove-1); /* before DisplayMoveError */
8351         SwitchClocks(forwardMostMove-1); // [HGM] race
8352         DisplayBothClocks();
8353         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8354                 parseList[currentMove], _(cps->which));
8355         DisplayMoveError(buf1);
8356         DrawPosition(FALSE, boards[currentMove]);
8357
8358         SetUserThinkingEnables();
8359         return;
8360     }
8361     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8362         /* Program has a broken "time" command that
8363            outputs a string not ending in newline.
8364            Don't use it. */
8365         cps->sendTime = 0;
8366     }
8367
8368     /*
8369      * If chess program startup fails, exit with an error message.
8370      * Attempts to recover here are futile.
8371      */
8372     if ((StrStr(message, "unknown host") != NULL)
8373         || (StrStr(message, "No remote directory") != NULL)
8374         || (StrStr(message, "not found") != NULL)
8375         || (StrStr(message, "No such file") != NULL)
8376         || (StrStr(message, "can't alloc") != NULL)
8377         || (StrStr(message, "Permission denied") != NULL)) {
8378
8379         cps->maybeThinking = FALSE;
8380         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8381                 _(cps->which), cps->program, cps->host, message);
8382         RemoveInputSource(cps->isr);
8383         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8384             if(cps == &first) appData.noChessProgram = TRUE;
8385             DisplayError(buf1, 0);
8386         }
8387         return;
8388     }
8389
8390     /*
8391      * Look for hint output
8392      */
8393     if (sscanf(message, "Hint: %s", buf1) == 1) {
8394         if (cps == &first && hintRequested) {
8395             hintRequested = FALSE;
8396             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8397                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8398                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8399                                     PosFlags(forwardMostMove),
8400                                     fromY, fromX, toY, toX, promoChar, buf1);
8401                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8402                 DisplayInformation(buf2);
8403             } else {
8404                 /* Hint move could not be parsed!? */
8405               snprintf(buf2, sizeof(buf2),
8406                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8407                         buf1, _(cps->which));
8408                 DisplayError(buf2, 0);
8409             }
8410         } else {
8411           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8412         }
8413         return;
8414     }
8415
8416     /*
8417      * Ignore other messages if game is not in progress
8418      */
8419     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8420         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8421
8422     /*
8423      * look for win, lose, draw, or draw offer
8424      */
8425     if (strncmp(message, "1-0", 3) == 0) {
8426         char *p, *q, *r = "";
8427         p = strchr(message, '{');
8428         if (p) {
8429             q = strchr(p, '}');
8430             if (q) {
8431                 *q = NULLCHAR;
8432                 r = p + 1;
8433             }
8434         }
8435         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8436         return;
8437     } else if (strncmp(message, "0-1", 3) == 0) {
8438         char *p, *q, *r = "";
8439         p = strchr(message, '{');
8440         if (p) {
8441             q = strchr(p, '}');
8442             if (q) {
8443                 *q = NULLCHAR;
8444                 r = p + 1;
8445             }
8446         }
8447         /* Kludge for Arasan 4.1 bug */
8448         if (strcmp(r, "Black resigns") == 0) {
8449             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8450             return;
8451         }
8452         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8453         return;
8454     } else if (strncmp(message, "1/2", 3) == 0) {
8455         char *p, *q, *r = "";
8456         p = strchr(message, '{');
8457         if (p) {
8458             q = strchr(p, '}');
8459             if (q) {
8460                 *q = NULLCHAR;
8461                 r = p + 1;
8462             }
8463         }
8464
8465         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8466         return;
8467
8468     } else if (strncmp(message, "White resign", 12) == 0) {
8469         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8470         return;
8471     } else if (strncmp(message, "Black resign", 12) == 0) {
8472         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8473         return;
8474     } else if (strncmp(message, "White matches", 13) == 0 ||
8475                strncmp(message, "Black matches", 13) == 0   ) {
8476         /* [HGM] ignore GNUShogi noises */
8477         return;
8478     } else if (strncmp(message, "White", 5) == 0 &&
8479                message[5] != '(' &&
8480                StrStr(message, "Black") == NULL) {
8481         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8482         return;
8483     } else if (strncmp(message, "Black", 5) == 0 &&
8484                message[5] != '(') {
8485         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8486         return;
8487     } else if (strcmp(message, "resign") == 0 ||
8488                strcmp(message, "computer resigns") == 0) {
8489         switch (gameMode) {
8490           case MachinePlaysBlack:
8491           case IcsPlayingBlack:
8492             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8493             break;
8494           case MachinePlaysWhite:
8495           case IcsPlayingWhite:
8496             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8497             break;
8498           case TwoMachinesPlay:
8499             if (cps->twoMachinesColor[0] == 'w')
8500               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8501             else
8502               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8503             break;
8504           default:
8505             /* can't happen */
8506             break;
8507         }
8508         return;
8509     } else if (strncmp(message, "opponent mates", 14) == 0) {
8510         switch (gameMode) {
8511           case MachinePlaysBlack:
8512           case IcsPlayingBlack:
8513             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8514             break;
8515           case MachinePlaysWhite:
8516           case IcsPlayingWhite:
8517             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8518             break;
8519           case TwoMachinesPlay:
8520             if (cps->twoMachinesColor[0] == 'w')
8521               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8522             else
8523               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8524             break;
8525           default:
8526             /* can't happen */
8527             break;
8528         }
8529         return;
8530     } else if (strncmp(message, "computer mates", 14) == 0) {
8531         switch (gameMode) {
8532           case MachinePlaysBlack:
8533           case IcsPlayingBlack:
8534             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8535             break;
8536           case MachinePlaysWhite:
8537           case IcsPlayingWhite:
8538             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8539             break;
8540           case TwoMachinesPlay:
8541             if (cps->twoMachinesColor[0] == 'w')
8542               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8543             else
8544               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8545             break;
8546           default:
8547             /* can't happen */
8548             break;
8549         }
8550         return;
8551     } else if (strncmp(message, "checkmate", 9) == 0) {
8552         if (WhiteOnMove(forwardMostMove)) {
8553             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8554         } else {
8555             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8556         }
8557         return;
8558     } else if (strstr(message, "Draw") != NULL ||
8559                strstr(message, "game is a draw") != NULL) {
8560         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8561         return;
8562     } else if (strstr(message, "offer") != NULL &&
8563                strstr(message, "draw") != NULL) {
8564 #if ZIPPY
8565         if (appData.zippyPlay && first.initDone) {
8566             /* Relay offer to ICS */
8567             SendToICS(ics_prefix);
8568             SendToICS("draw\n");
8569         }
8570 #endif
8571         cps->offeredDraw = 2; /* valid until this engine moves twice */
8572         if (gameMode == TwoMachinesPlay) {
8573             if (cps->other->offeredDraw) {
8574                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8575             /* [HGM] in two-machine mode we delay relaying draw offer      */
8576             /* until after we also have move, to see if it is really claim */
8577             }
8578         } else if (gameMode == MachinePlaysWhite ||
8579                    gameMode == MachinePlaysBlack) {
8580           if (userOfferedDraw) {
8581             DisplayInformation(_("Machine accepts your draw offer"));
8582             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8583           } else {
8584             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8585           }
8586         }
8587     }
8588
8589
8590     /*
8591      * Look for thinking output
8592      */
8593     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8594           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8595                                 ) {
8596         int plylev, mvleft, mvtot, curscore, time;
8597         char mvname[MOVE_LEN];
8598         u64 nodes; // [DM]
8599         char plyext;
8600         int ignore = FALSE;
8601         int prefixHint = FALSE;
8602         mvname[0] = NULLCHAR;
8603
8604         switch (gameMode) {
8605           case MachinePlaysBlack:
8606           case IcsPlayingBlack:
8607             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8608             break;
8609           case MachinePlaysWhite:
8610           case IcsPlayingWhite:
8611             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8612             break;
8613           case AnalyzeMode:
8614           case AnalyzeFile:
8615             break;
8616           case IcsObserving: /* [DM] icsEngineAnalyze */
8617             if (!appData.icsEngineAnalyze) ignore = TRUE;
8618             break;
8619           case TwoMachinesPlay:
8620             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8621                 ignore = TRUE;
8622             }
8623             break;
8624           default:
8625             ignore = TRUE;
8626             break;
8627         }
8628
8629         if (!ignore) {
8630             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8631             buf1[0] = NULLCHAR;
8632             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8633                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8634
8635                 if (plyext != ' ' && plyext != '\t') {
8636                     time *= 100;
8637                 }
8638
8639                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8640                 if( cps->scoreIsAbsolute &&
8641                     ( gameMode == MachinePlaysBlack ||
8642                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8643                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8644                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8645                      !WhiteOnMove(currentMove)
8646                     ) )
8647                 {
8648                     curscore = -curscore;
8649                 }
8650
8651                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8652
8653                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8654                         char buf[MSG_SIZ];
8655                         FILE *f;
8656                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8657                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8658                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8659                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8660                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8661                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8662                                 fclose(f);
8663                         } else DisplayError(_("failed writing PV"), 0);
8664                 }
8665
8666                 tempStats.depth = plylev;
8667                 tempStats.nodes = nodes;
8668                 tempStats.time = time;
8669                 tempStats.score = curscore;
8670                 tempStats.got_only_move = 0;
8671
8672                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8673                         int ticklen;
8674
8675                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8676                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8677                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8678                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8679                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8680                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8681                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8682                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8683                 }
8684
8685                 /* Buffer overflow protection */
8686                 if (pv[0] != NULLCHAR) {
8687                     if (strlen(pv) >= sizeof(tempStats.movelist)
8688                         && appData.debugMode) {
8689                         fprintf(debugFP,
8690                                 "PV is too long; using the first %u bytes.\n",
8691                                 (unsigned) sizeof(tempStats.movelist) - 1);
8692                     }
8693
8694                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8695                 } else {
8696                     sprintf(tempStats.movelist, " no PV\n");
8697                 }
8698
8699                 if (tempStats.seen_stat) {
8700                     tempStats.ok_to_send = 1;
8701                 }
8702
8703                 if (strchr(tempStats.movelist, '(') != NULL) {
8704                     tempStats.line_is_book = 1;
8705                     tempStats.nr_moves = 0;
8706                     tempStats.moves_left = 0;
8707                 } else {
8708                     tempStats.line_is_book = 0;
8709                 }
8710
8711                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8712                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8713
8714                 SendProgramStatsToFrontend( cps, &tempStats );
8715
8716                 /*
8717                     [AS] Protect the thinkOutput buffer from overflow... this
8718                     is only useful if buf1 hasn't overflowed first!
8719                 */
8720                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8721                          plylev,
8722                          (gameMode == TwoMachinesPlay ?
8723                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8724                          ((double) curscore) / 100.0,
8725                          prefixHint ? lastHint : "",
8726                          prefixHint ? " " : "" );
8727
8728                 if( buf1[0] != NULLCHAR ) {
8729                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8730
8731                     if( strlen(pv) > max_len ) {
8732                         if( appData.debugMode) {
8733                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8734                         }
8735                         pv[max_len+1] = '\0';
8736                     }
8737
8738                     strcat( thinkOutput, pv);
8739                 }
8740
8741                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8742                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8743                     DisplayMove(currentMove - 1);
8744                 }
8745                 return;
8746
8747             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8748                 /* crafty (9.25+) says "(only move) <move>"
8749                  * if there is only 1 legal move
8750                  */
8751                 sscanf(p, "(only move) %s", buf1);
8752                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8753                 sprintf(programStats.movelist, "%s (only move)", buf1);
8754                 programStats.depth = 1;
8755                 programStats.nr_moves = 1;
8756                 programStats.moves_left = 1;
8757                 programStats.nodes = 1;
8758                 programStats.time = 1;
8759                 programStats.got_only_move = 1;
8760
8761                 /* Not really, but we also use this member to
8762                    mean "line isn't going to change" (Crafty
8763                    isn't searching, so stats won't change) */
8764                 programStats.line_is_book = 1;
8765
8766                 SendProgramStatsToFrontend( cps, &programStats );
8767
8768                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8769                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8770                     DisplayMove(currentMove - 1);
8771                 }
8772                 return;
8773             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8774                               &time, &nodes, &plylev, &mvleft,
8775                               &mvtot, mvname) >= 5) {
8776                 /* The stat01: line is from Crafty (9.29+) in response
8777                    to the "." command */
8778                 programStats.seen_stat = 1;
8779                 cps->maybeThinking = TRUE;
8780
8781                 if (programStats.got_only_move || !appData.periodicUpdates)
8782                   return;
8783
8784                 programStats.depth = plylev;
8785                 programStats.time = time;
8786                 programStats.nodes = nodes;
8787                 programStats.moves_left = mvleft;
8788                 programStats.nr_moves = mvtot;
8789                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8790                 programStats.ok_to_send = 1;
8791                 programStats.movelist[0] = '\0';
8792
8793                 SendProgramStatsToFrontend( cps, &programStats );
8794
8795                 return;
8796
8797             } else if (strncmp(message,"++",2) == 0) {
8798                 /* Crafty 9.29+ outputs this */
8799                 programStats.got_fail = 2;
8800                 return;
8801
8802             } else if (strncmp(message,"--",2) == 0) {
8803                 /* Crafty 9.29+ outputs this */
8804                 programStats.got_fail = 1;
8805                 return;
8806
8807             } else if (thinkOutput[0] != NULLCHAR &&
8808                        strncmp(message, "    ", 4) == 0) {
8809                 unsigned message_len;
8810
8811                 p = message;
8812                 while (*p && *p == ' ') p++;
8813
8814                 message_len = strlen( p );
8815
8816                 /* [AS] Avoid buffer overflow */
8817                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8818                     strcat(thinkOutput, " ");
8819                     strcat(thinkOutput, p);
8820                 }
8821
8822                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8823                     strcat(programStats.movelist, " ");
8824                     strcat(programStats.movelist, p);
8825                 }
8826
8827                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8828                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8829                     DisplayMove(currentMove - 1);
8830                 }
8831                 return;
8832             }
8833         }
8834         else {
8835             buf1[0] = NULLCHAR;
8836
8837             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8838                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8839             {
8840                 ChessProgramStats cpstats;
8841
8842                 if (plyext != ' ' && plyext != '\t') {
8843                     time *= 100;
8844                 }
8845
8846                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8847                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8848                     curscore = -curscore;
8849                 }
8850
8851                 cpstats.depth = plylev;
8852                 cpstats.nodes = nodes;
8853                 cpstats.time = time;
8854                 cpstats.score = curscore;
8855                 cpstats.got_only_move = 0;
8856                 cpstats.movelist[0] = '\0';
8857
8858                 if (buf1[0] != NULLCHAR) {
8859                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8860                 }
8861
8862                 cpstats.ok_to_send = 0;
8863                 cpstats.line_is_book = 0;
8864                 cpstats.nr_moves = 0;
8865                 cpstats.moves_left = 0;
8866
8867                 SendProgramStatsToFrontend( cps, &cpstats );
8868             }
8869         }
8870     }
8871 }
8872
8873
8874 /* Parse a game score from the character string "game", and
8875    record it as the history of the current game.  The game
8876    score is NOT assumed to start from the standard position.
8877    The display is not updated in any way.
8878    */
8879 void
8880 ParseGameHistory (char *game)
8881 {
8882     ChessMove moveType;
8883     int fromX, fromY, toX, toY, boardIndex;
8884     char promoChar;
8885     char *p, *q;
8886     char buf[MSG_SIZ];
8887
8888     if (appData.debugMode)
8889       fprintf(debugFP, "Parsing game history: %s\n", game);
8890
8891     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8892     gameInfo.site = StrSave(appData.icsHost);
8893     gameInfo.date = PGNDate();
8894     gameInfo.round = StrSave("-");
8895
8896     /* Parse out names of players */
8897     while (*game == ' ') game++;
8898     p = buf;
8899     while (*game != ' ') *p++ = *game++;
8900     *p = NULLCHAR;
8901     gameInfo.white = StrSave(buf);
8902     while (*game == ' ') game++;
8903     p = buf;
8904     while (*game != ' ' && *game != '\n') *p++ = *game++;
8905     *p = NULLCHAR;
8906     gameInfo.black = StrSave(buf);
8907
8908     /* Parse moves */
8909     boardIndex = blackPlaysFirst ? 1 : 0;
8910     yynewstr(game);
8911     for (;;) {
8912         yyboardindex = boardIndex;
8913         moveType = (ChessMove) Myylex();
8914         switch (moveType) {
8915           case IllegalMove:             /* maybe suicide chess, etc. */
8916   if (appData.debugMode) {
8917     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8918     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8919     setbuf(debugFP, NULL);
8920   }
8921           case WhitePromotion:
8922           case BlackPromotion:
8923           case WhiteNonPromotion:
8924           case BlackNonPromotion:
8925           case NormalMove:
8926           case WhiteCapturesEnPassant:
8927           case BlackCapturesEnPassant:
8928           case WhiteKingSideCastle:
8929           case WhiteQueenSideCastle:
8930           case BlackKingSideCastle:
8931           case BlackQueenSideCastle:
8932           case WhiteKingSideCastleWild:
8933           case WhiteQueenSideCastleWild:
8934           case BlackKingSideCastleWild:
8935           case BlackQueenSideCastleWild:
8936           /* PUSH Fabien */
8937           case WhiteHSideCastleFR:
8938           case WhiteASideCastleFR:
8939           case BlackHSideCastleFR:
8940           case BlackASideCastleFR:
8941           /* POP Fabien */
8942             fromX = currentMoveString[0] - AAA;
8943             fromY = currentMoveString[1] - ONE;
8944             toX = currentMoveString[2] - AAA;
8945             toY = currentMoveString[3] - ONE;
8946             promoChar = currentMoveString[4];
8947             break;
8948           case WhiteDrop:
8949           case BlackDrop:
8950             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8951             fromX = moveType == WhiteDrop ?
8952               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8953             (int) CharToPiece(ToLower(currentMoveString[0]));
8954             fromY = DROP_RANK;
8955             toX = currentMoveString[2] - AAA;
8956             toY = currentMoveString[3] - ONE;
8957             promoChar = NULLCHAR;
8958             break;
8959           case AmbiguousMove:
8960             /* bug? */
8961             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8962   if (appData.debugMode) {
8963     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8964     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8965     setbuf(debugFP, NULL);
8966   }
8967             DisplayError(buf, 0);
8968             return;
8969           case ImpossibleMove:
8970             /* bug? */
8971             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8972   if (appData.debugMode) {
8973     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8974     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8975     setbuf(debugFP, NULL);
8976   }
8977             DisplayError(buf, 0);
8978             return;
8979           case EndOfFile:
8980             if (boardIndex < backwardMostMove) {
8981                 /* Oops, gap.  How did that happen? */
8982                 DisplayError(_("Gap in move list"), 0);
8983                 return;
8984             }
8985             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8986             if (boardIndex > forwardMostMove) {
8987                 forwardMostMove = boardIndex;
8988             }
8989             return;
8990           case ElapsedTime:
8991             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8992                 strcat(parseList[boardIndex-1], " ");
8993                 strcat(parseList[boardIndex-1], yy_text);
8994             }
8995             continue;
8996           case Comment:
8997           case PGNTag:
8998           case NAG:
8999           default:
9000             /* ignore */
9001             continue;
9002           case WhiteWins:
9003           case BlackWins:
9004           case GameIsDrawn:
9005           case GameUnfinished:
9006             if (gameMode == IcsExamining) {
9007                 if (boardIndex < backwardMostMove) {
9008                     /* Oops, gap.  How did that happen? */
9009                     return;
9010                 }
9011                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9012                 return;
9013             }
9014             gameInfo.result = moveType;
9015             p = strchr(yy_text, '{');
9016             if (p == NULL) p = strchr(yy_text, '(');
9017             if (p == NULL) {
9018                 p = yy_text;
9019                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9020             } else {
9021                 q = strchr(p, *p == '{' ? '}' : ')');
9022                 if (q != NULL) *q = NULLCHAR;
9023                 p++;
9024             }
9025             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9026             gameInfo.resultDetails = StrSave(p);
9027             continue;
9028         }
9029         if (boardIndex >= forwardMostMove &&
9030             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9031             backwardMostMove = blackPlaysFirst ? 1 : 0;
9032             return;
9033         }
9034         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9035                                  fromY, fromX, toY, toX, promoChar,
9036                                  parseList[boardIndex]);
9037         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9038         /* currentMoveString is set as a side-effect of yylex */
9039         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9040         strcat(moveList[boardIndex], "\n");
9041         boardIndex++;
9042         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9043         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9044           case MT_NONE:
9045           case MT_STALEMATE:
9046           default:
9047             break;
9048           case MT_CHECK:
9049             if(gameInfo.variant != VariantShogi)
9050                 strcat(parseList[boardIndex - 1], "+");
9051             break;
9052           case MT_CHECKMATE:
9053           case MT_STAINMATE:
9054             strcat(parseList[boardIndex - 1], "#");
9055             break;
9056         }
9057     }
9058 }
9059
9060
9061 /* Apply a move to the given board  */
9062 void
9063 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9064 {
9065   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9066   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9067
9068     /* [HGM] compute & store e.p. status and castling rights for new position */
9069     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9070
9071       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9072       oldEP = (signed char)board[EP_STATUS];
9073       board[EP_STATUS] = EP_NONE;
9074
9075   if (fromY == DROP_RANK) {
9076         /* must be first */
9077         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9078             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9079             return;
9080         }
9081         piece = board[toY][toX] = (ChessSquare) fromX;
9082   } else {
9083       int i;
9084
9085       if( board[toY][toX] != EmptySquare )
9086            board[EP_STATUS] = EP_CAPTURE;
9087
9088       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9089            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9090                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9091       } else
9092       if( board[fromY][fromX] == WhitePawn ) {
9093            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9094                board[EP_STATUS] = EP_PAWN_MOVE;
9095            if( toY-fromY==2) {
9096                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9097                         gameInfo.variant != VariantBerolina || toX < fromX)
9098                       board[EP_STATUS] = toX | berolina;
9099                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9100                         gameInfo.variant != VariantBerolina || toX > fromX)
9101                       board[EP_STATUS] = toX;
9102            }
9103       } else
9104       if( board[fromY][fromX] == BlackPawn ) {
9105            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9106                board[EP_STATUS] = EP_PAWN_MOVE;
9107            if( toY-fromY== -2) {
9108                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9109                         gameInfo.variant != VariantBerolina || toX < fromX)
9110                       board[EP_STATUS] = toX | berolina;
9111                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9112                         gameInfo.variant != VariantBerolina || toX > fromX)
9113                       board[EP_STATUS] = toX;
9114            }
9115        }
9116
9117        for(i=0; i<nrCastlingRights; i++) {
9118            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9119               board[CASTLING][i] == toX   && castlingRank[i] == toY
9120              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9121        }
9122
9123      if (fromX == toX && fromY == toY) return;
9124
9125      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9126      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9127      if(gameInfo.variant == VariantKnightmate)
9128          king += (int) WhiteUnicorn - (int) WhiteKing;
9129
9130     /* Code added by Tord: */
9131     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9132     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9133         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9134       board[fromY][fromX] = EmptySquare;
9135       board[toY][toX] = EmptySquare;
9136       if((toX > fromX) != (piece == WhiteRook)) {
9137         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9138       } else {
9139         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9140       }
9141     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9142                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9143       board[fromY][fromX] = EmptySquare;
9144       board[toY][toX] = EmptySquare;
9145       if((toX > fromX) != (piece == BlackRook)) {
9146         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9147       } else {
9148         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9149       }
9150     /* End of code added by Tord */
9151
9152     } else if (board[fromY][fromX] == king
9153         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9154         && toY == fromY && toX > fromX+1) {
9155         board[fromY][fromX] = EmptySquare;
9156         board[toY][toX] = king;
9157         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9158         board[fromY][BOARD_RGHT-1] = EmptySquare;
9159     } else if (board[fromY][fromX] == king
9160         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9161                && toY == fromY && toX < fromX-1) {
9162         board[fromY][fromX] = EmptySquare;
9163         board[toY][toX] = king;
9164         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9165         board[fromY][BOARD_LEFT] = EmptySquare;
9166     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9167                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9168                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9169                ) {
9170         /* white pawn promotion */
9171         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9172         if(gameInfo.variant==VariantBughouse ||
9173            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9174             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9175         board[fromY][fromX] = EmptySquare;
9176     } else if ((fromY >= BOARD_HEIGHT>>1)
9177                && (toX != fromX)
9178                && gameInfo.variant != VariantXiangqi
9179                && gameInfo.variant != VariantBerolina
9180                && (board[fromY][fromX] == WhitePawn)
9181                && (board[toY][toX] == EmptySquare)) {
9182         board[fromY][fromX] = EmptySquare;
9183         board[toY][toX] = WhitePawn;
9184         captured = board[toY - 1][toX];
9185         board[toY - 1][toX] = EmptySquare;
9186     } else if ((fromY == BOARD_HEIGHT-4)
9187                && (toX == fromX)
9188                && gameInfo.variant == VariantBerolina
9189                && (board[fromY][fromX] == WhitePawn)
9190                && (board[toY][toX] == EmptySquare)) {
9191         board[fromY][fromX] = EmptySquare;
9192         board[toY][toX] = WhitePawn;
9193         if(oldEP & EP_BEROLIN_A) {
9194                 captured = board[fromY][fromX-1];
9195                 board[fromY][fromX-1] = EmptySquare;
9196         }else{  captured = board[fromY][fromX+1];
9197                 board[fromY][fromX+1] = EmptySquare;
9198         }
9199     } else if (board[fromY][fromX] == king
9200         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9201                && toY == fromY && toX > fromX+1) {
9202         board[fromY][fromX] = EmptySquare;
9203         board[toY][toX] = king;
9204         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9205         board[fromY][BOARD_RGHT-1] = EmptySquare;
9206     } else if (board[fromY][fromX] == king
9207         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9208                && toY == fromY && toX < fromX-1) {
9209         board[fromY][fromX] = EmptySquare;
9210         board[toY][toX] = king;
9211         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9212         board[fromY][BOARD_LEFT] = EmptySquare;
9213     } else if (fromY == 7 && fromX == 3
9214                && board[fromY][fromX] == BlackKing
9215                && toY == 7 && toX == 5) {
9216         board[fromY][fromX] = EmptySquare;
9217         board[toY][toX] = BlackKing;
9218         board[fromY][7] = EmptySquare;
9219         board[toY][4] = BlackRook;
9220     } else if (fromY == 7 && fromX == 3
9221                && board[fromY][fromX] == BlackKing
9222                && toY == 7 && toX == 1) {
9223         board[fromY][fromX] = EmptySquare;
9224         board[toY][toX] = BlackKing;
9225         board[fromY][0] = EmptySquare;
9226         board[toY][2] = BlackRook;
9227     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9228                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9229                && toY < promoRank && promoChar
9230                ) {
9231         /* black pawn promotion */
9232         board[toY][toX] = CharToPiece(ToLower(promoChar));
9233         if(gameInfo.variant==VariantBughouse ||
9234            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9235             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9236         board[fromY][fromX] = EmptySquare;
9237     } else if ((fromY < BOARD_HEIGHT>>1)
9238                && (toX != fromX)
9239                && gameInfo.variant != VariantXiangqi
9240                && gameInfo.variant != VariantBerolina
9241                && (board[fromY][fromX] == BlackPawn)
9242                && (board[toY][toX] == EmptySquare)) {
9243         board[fromY][fromX] = EmptySquare;
9244         board[toY][toX] = BlackPawn;
9245         captured = board[toY + 1][toX];
9246         board[toY + 1][toX] = EmptySquare;
9247     } else if ((fromY == 3)
9248                && (toX == fromX)
9249                && gameInfo.variant == VariantBerolina
9250                && (board[fromY][fromX] == BlackPawn)
9251                && (board[toY][toX] == EmptySquare)) {
9252         board[fromY][fromX] = EmptySquare;
9253         board[toY][toX] = BlackPawn;
9254         if(oldEP & EP_BEROLIN_A) {
9255                 captured = board[fromY][fromX-1];
9256                 board[fromY][fromX-1] = EmptySquare;
9257         }else{  captured = board[fromY][fromX+1];
9258                 board[fromY][fromX+1] = EmptySquare;
9259         }
9260     } else {
9261         board[toY][toX] = board[fromY][fromX];
9262         board[fromY][fromX] = EmptySquare;
9263     }
9264   }
9265
9266     if (gameInfo.holdingsWidth != 0) {
9267
9268       /* !!A lot more code needs to be written to support holdings  */
9269       /* [HGM] OK, so I have written it. Holdings are stored in the */
9270       /* penultimate board files, so they are automaticlly stored   */
9271       /* in the game history.                                       */
9272       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9273                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9274         /* Delete from holdings, by decreasing count */
9275         /* and erasing image if necessary            */
9276         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9277         if(p < (int) BlackPawn) { /* white drop */
9278              p -= (int)WhitePawn;
9279                  p = PieceToNumber((ChessSquare)p);
9280              if(p >= gameInfo.holdingsSize) p = 0;
9281              if(--board[p][BOARD_WIDTH-2] <= 0)
9282                   board[p][BOARD_WIDTH-1] = EmptySquare;
9283              if((int)board[p][BOARD_WIDTH-2] < 0)
9284                         board[p][BOARD_WIDTH-2] = 0;
9285         } else {                  /* black drop */
9286              p -= (int)BlackPawn;
9287                  p = PieceToNumber((ChessSquare)p);
9288              if(p >= gameInfo.holdingsSize) p = 0;
9289              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9290                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9291              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9292                         board[BOARD_HEIGHT-1-p][1] = 0;
9293         }
9294       }
9295       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9296           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9297         /* [HGM] holdings: Add to holdings, if holdings exist */
9298         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9299                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9300                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9301         }
9302         p = (int) captured;
9303         if (p >= (int) BlackPawn) {
9304           p -= (int)BlackPawn;
9305           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9306                   /* in Shogi restore piece to its original  first */
9307                   captured = (ChessSquare) (DEMOTED captured);
9308                   p = DEMOTED p;
9309           }
9310           p = PieceToNumber((ChessSquare)p);
9311           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9312           board[p][BOARD_WIDTH-2]++;
9313           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9314         } else {
9315           p -= (int)WhitePawn;
9316           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9317                   captured = (ChessSquare) (DEMOTED captured);
9318                   p = DEMOTED p;
9319           }
9320           p = PieceToNumber((ChessSquare)p);
9321           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9322           board[BOARD_HEIGHT-1-p][1]++;
9323           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9324         }
9325       }
9326     } else if (gameInfo.variant == VariantAtomic) {
9327       if (captured != EmptySquare) {
9328         int y, x;
9329         for (y = toY-1; y <= toY+1; y++) {
9330           for (x = toX-1; x <= toX+1; x++) {
9331             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9332                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9333               board[y][x] = EmptySquare;
9334             }
9335           }
9336         }
9337         board[toY][toX] = EmptySquare;
9338       }
9339     }
9340     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9341         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9342     } else
9343     if(promoChar == '+') {
9344         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9345         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9346     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9347         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9348     }
9349     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9350                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9351         // [HGM] superchess: take promotion piece out of holdings
9352         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9353         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9354             if(!--board[k][BOARD_WIDTH-2])
9355                 board[k][BOARD_WIDTH-1] = EmptySquare;
9356         } else {
9357             if(!--board[BOARD_HEIGHT-1-k][1])
9358                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9359         }
9360     }
9361
9362 }
9363
9364 /* Updates forwardMostMove */
9365 void
9366 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9367 {
9368 //    forwardMostMove++; // [HGM] bare: moved downstream
9369
9370     (void) CoordsToAlgebraic(boards[forwardMostMove],
9371                              PosFlags(forwardMostMove),
9372                              fromY, fromX, toY, toX, promoChar,
9373                              parseList[forwardMostMove]);
9374
9375     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9376         int timeLeft; static int lastLoadFlag=0; int king, piece;
9377         piece = boards[forwardMostMove][fromY][fromX];
9378         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9379         if(gameInfo.variant == VariantKnightmate)
9380             king += (int) WhiteUnicorn - (int) WhiteKing;
9381         if(forwardMostMove == 0) {
9382             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9383                 fprintf(serverMoves, "%s;", UserName());
9384             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9385                 fprintf(serverMoves, "%s;", second.tidy);
9386             fprintf(serverMoves, "%s;", first.tidy);
9387             if(gameMode == MachinePlaysWhite)
9388                 fprintf(serverMoves, "%s;", UserName());
9389             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9390                 fprintf(serverMoves, "%s;", second.tidy);
9391         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9392         lastLoadFlag = loadFlag;
9393         // print base move
9394         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9395         // print castling suffix
9396         if( toY == fromY && piece == king ) {
9397             if(toX-fromX > 1)
9398                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9399             if(fromX-toX >1)
9400                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9401         }
9402         // e.p. suffix
9403         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9404              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9405              boards[forwardMostMove][toY][toX] == EmptySquare
9406              && fromX != toX && fromY != toY)
9407                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9408         // promotion suffix
9409         if(promoChar != NULLCHAR)
9410                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9411         if(!loadFlag) {
9412                 char buf[MOVE_LEN*2], *p; int len;
9413             fprintf(serverMoves, "/%d/%d",
9414                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9415             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9416             else                      timeLeft = blackTimeRemaining/1000;
9417             fprintf(serverMoves, "/%d", timeLeft);
9418                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9419                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9420                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9421             fprintf(serverMoves, "/%s", buf);
9422         }
9423         fflush(serverMoves);
9424     }
9425
9426     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9427         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9428       return;
9429     }
9430     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9431     if (commentList[forwardMostMove+1] != NULL) {
9432         free(commentList[forwardMostMove+1]);
9433         commentList[forwardMostMove+1] = NULL;
9434     }
9435     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9436     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9437     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9438     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9439     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9440     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9441     adjustedClock = FALSE;
9442     gameInfo.result = GameUnfinished;
9443     if (gameInfo.resultDetails != NULL) {
9444         free(gameInfo.resultDetails);
9445         gameInfo.resultDetails = NULL;
9446     }
9447     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9448                               moveList[forwardMostMove - 1]);
9449     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9450       case MT_NONE:
9451       case MT_STALEMATE:
9452       default:
9453         break;
9454       case MT_CHECK:
9455         if(gameInfo.variant != VariantShogi)
9456             strcat(parseList[forwardMostMove - 1], "+");
9457         break;
9458       case MT_CHECKMATE:
9459       case MT_STAINMATE:
9460         strcat(parseList[forwardMostMove - 1], "#");
9461         break;
9462     }
9463     if (appData.debugMode) {
9464         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9465     }
9466
9467 }
9468
9469 /* Updates currentMove if not pausing */
9470 void
9471 ShowMove (int fromX, int fromY, int toX, int toY)
9472 {
9473     int instant = (gameMode == PlayFromGameFile) ?
9474         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9475     if(appData.noGUI) return;
9476     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9477         if (!instant) {
9478             if (forwardMostMove == currentMove + 1) {
9479                 AnimateMove(boards[forwardMostMove - 1],
9480                             fromX, fromY, toX, toY);
9481             }
9482             if (appData.highlightLastMove) {
9483                 SetHighlights(fromX, fromY, toX, toY);
9484             }
9485         }
9486         currentMove = forwardMostMove;
9487     }
9488
9489     if (instant) return;
9490
9491     DisplayMove(currentMove - 1);
9492     DrawPosition(FALSE, boards[currentMove]);
9493     DisplayBothClocks();
9494     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9495 }
9496
9497 void
9498 SendEgtPath (ChessProgramState *cps)
9499 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9500         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9501
9502         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9503
9504         while(*p) {
9505             char c, *q = name+1, *r, *s;
9506
9507             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9508             while(*p && *p != ',') *q++ = *p++;
9509             *q++ = ':'; *q = 0;
9510             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9511                 strcmp(name, ",nalimov:") == 0 ) {
9512                 // take nalimov path from the menu-changeable option first, if it is defined
9513               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9514                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9515             } else
9516             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9517                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9518                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9519                 s = r = StrStr(s, ":") + 1; // beginning of path info
9520                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9521                 c = *r; *r = 0;             // temporarily null-terminate path info
9522                     *--q = 0;               // strip of trailig ':' from name
9523                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9524                 *r = c;
9525                 SendToProgram(buf,cps);     // send egtbpath command for this format
9526             }
9527             if(*p == ',') p++; // read away comma to position for next format name
9528         }
9529 }
9530
9531 void
9532 InitChessProgram (ChessProgramState *cps, int setup)
9533 /* setup needed to setup FRC opening position */
9534 {
9535     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9536     if (appData.noChessProgram) return;
9537     hintRequested = FALSE;
9538     bookRequested = FALSE;
9539
9540     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9541     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9542     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9543     if(cps->memSize) { /* [HGM] memory */
9544       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9545         SendToProgram(buf, cps);
9546     }
9547     SendEgtPath(cps); /* [HGM] EGT */
9548     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9549       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9550         SendToProgram(buf, cps);
9551     }
9552
9553     SendToProgram(cps->initString, cps);
9554     if (gameInfo.variant != VariantNormal &&
9555         gameInfo.variant != VariantLoadable
9556         /* [HGM] also send variant if board size non-standard */
9557         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9558                                             ) {
9559       char *v = VariantName(gameInfo.variant);
9560       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9561         /* [HGM] in protocol 1 we have to assume all variants valid */
9562         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9563         DisplayFatalError(buf, 0, 1);
9564         return;
9565       }
9566
9567       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9568       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9569       if( gameInfo.variant == VariantXiangqi )
9570            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9571       if( gameInfo.variant == VariantShogi )
9572            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9573       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9574            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9575       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9576           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9577            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9578       if( gameInfo.variant == VariantCourier )
9579            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9580       if( gameInfo.variant == VariantSuper )
9581            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9582       if( gameInfo.variant == VariantGreat )
9583            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9584       if( gameInfo.variant == VariantSChess )
9585            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9586       if( gameInfo.variant == VariantGrand )
9587            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9588
9589       if(overruled) {
9590         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9591                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9592            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9593            if(StrStr(cps->variants, b) == NULL) {
9594                // specific sized variant not known, check if general sizing allowed
9595                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9596                    if(StrStr(cps->variants, "boardsize") == NULL) {
9597                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9598                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9599                        DisplayFatalError(buf, 0, 1);
9600                        return;
9601                    }
9602                    /* [HGM] here we really should compare with the maximum supported board size */
9603                }
9604            }
9605       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9606       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9607       SendToProgram(buf, cps);
9608     }
9609     currentlyInitializedVariant = gameInfo.variant;
9610
9611     /* [HGM] send opening position in FRC to first engine */
9612     if(setup) {
9613           SendToProgram("force\n", cps);
9614           SendBoard(cps, 0);
9615           /* engine is now in force mode! Set flag to wake it up after first move. */
9616           setboardSpoiledMachineBlack = 1;
9617     }
9618
9619     if (cps->sendICS) {
9620       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9621       SendToProgram(buf, cps);
9622     }
9623     cps->maybeThinking = FALSE;
9624     cps->offeredDraw = 0;
9625     if (!appData.icsActive) {
9626         SendTimeControl(cps, movesPerSession, timeControl,
9627                         timeIncrement, appData.searchDepth,
9628                         searchTime);
9629     }
9630     if (appData.showThinking
9631         // [HGM] thinking: four options require thinking output to be sent
9632         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9633                                 ) {
9634         SendToProgram("post\n", cps);
9635     }
9636     SendToProgram("hard\n", cps);
9637     if (!appData.ponderNextMove) {
9638         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9639            it without being sure what state we are in first.  "hard"
9640            is not a toggle, so that one is OK.
9641          */
9642         SendToProgram("easy\n", cps);
9643     }
9644     if (cps->usePing) {
9645       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9646       SendToProgram(buf, cps);
9647     }
9648     cps->initDone = TRUE;
9649     ClearEngineOutputPane(cps == &second);
9650 }
9651
9652
9653 void
9654 StartChessProgram (ChessProgramState *cps)
9655 {
9656     char buf[MSG_SIZ];
9657     int err;
9658
9659     if (appData.noChessProgram) return;
9660     cps->initDone = FALSE;
9661
9662     if (strcmp(cps->host, "localhost") == 0) {
9663         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9664     } else if (*appData.remoteShell == NULLCHAR) {
9665         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9666     } else {
9667         if (*appData.remoteUser == NULLCHAR) {
9668           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9669                     cps->program);
9670         } else {
9671           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9672                     cps->host, appData.remoteUser, cps->program);
9673         }
9674         err = StartChildProcess(buf, "", &cps->pr);
9675     }
9676
9677     if (err != 0) {
9678       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9679         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9680         if(cps != &first) return;
9681         appData.noChessProgram = TRUE;
9682         ThawUI();
9683         SetNCPMode();
9684 //      DisplayFatalError(buf, err, 1);
9685 //      cps->pr = NoProc;
9686 //      cps->isr = NULL;
9687         return;
9688     }
9689
9690     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9691     if (cps->protocolVersion > 1) {
9692       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9693       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9694       cps->comboCnt = 0;  //                and values of combo boxes
9695       SendToProgram(buf, cps);
9696     } else {
9697       SendToProgram("xboard\n", cps);
9698     }
9699 }
9700
9701 void
9702 TwoMachinesEventIfReady P((void))
9703 {
9704   static int curMess = 0;
9705   if (first.lastPing != first.lastPong) {
9706     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9707     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9708     return;
9709   }
9710   if (second.lastPing != second.lastPong) {
9711     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9712     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9713     return;
9714   }
9715   DisplayMessage("", ""); curMess = 0;
9716   ThawUI();
9717   TwoMachinesEvent();
9718 }
9719
9720 char *
9721 MakeName (char *template)
9722 {
9723     time_t clock;
9724     struct tm *tm;
9725     static char buf[MSG_SIZ];
9726     char *p = buf;
9727     int i;
9728
9729     clock = time((time_t *)NULL);
9730     tm = localtime(&clock);
9731
9732     while(*p++ = *template++) if(p[-1] == '%') {
9733         switch(*template++) {
9734           case 0:   *p = 0; return buf;
9735           case 'Y': i = tm->tm_year+1900; break;
9736           case 'y': i = tm->tm_year-100; break;
9737           case 'M': i = tm->tm_mon+1; break;
9738           case 'd': i = tm->tm_mday; break;
9739           case 'h': i = tm->tm_hour; break;
9740           case 'm': i = tm->tm_min; break;
9741           case 's': i = tm->tm_sec; break;
9742           default:  i = 0;
9743         }
9744         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9745     }
9746     return buf;
9747 }
9748
9749 int
9750 CountPlayers (char *p)
9751 {
9752     int n = 0;
9753     while(p = strchr(p, '\n')) p++, n++; // count participants
9754     return n;
9755 }
9756
9757 FILE *
9758 WriteTourneyFile (char *results, FILE *f)
9759 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9760     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9761     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9762         // create a file with tournament description
9763         fprintf(f, "-participants {%s}\n", appData.participants);
9764         fprintf(f, "-seedBase %d\n", appData.seedBase);
9765         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9766         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9767         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9768         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9769         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9770         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9771         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9772         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9773         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9774         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9775         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9776         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9777         if(searchTime > 0)
9778                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9779         else {
9780                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9781                 fprintf(f, "-tc %s\n", appData.timeControl);
9782                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9783         }
9784         fprintf(f, "-results \"%s\"\n", results);
9785     }
9786     return f;
9787 }
9788
9789 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9790
9791 void
9792 Substitute (char *participants, int expunge)
9793 {
9794     int i, changed, changes=0, nPlayers=0;
9795     char *p, *q, *r, buf[MSG_SIZ];
9796     if(participants == NULL) return;
9797     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9798     r = p = participants; q = appData.participants;
9799     while(*p && *p == *q) {
9800         if(*p == '\n') r = p+1, nPlayers++;
9801         p++; q++;
9802     }
9803     if(*p) { // difference
9804         while(*p && *p++ != '\n');
9805         while(*q && *q++ != '\n');
9806       changed = nPlayers;
9807         changes = 1 + (strcmp(p, q) != 0);
9808     }
9809     if(changes == 1) { // a single engine mnemonic was changed
9810         q = r; while(*q) nPlayers += (*q++ == '\n');
9811         p = buf; while(*r && (*p = *r++) != '\n') p++;
9812         *p = NULLCHAR;
9813         NamesToList(firstChessProgramNames, command, mnemonic, "all");
9814         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9815         if(mnemonic[i]) { // The substitute is valid
9816             FILE *f;
9817             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9818                 flock(fileno(f), LOCK_EX);
9819                 ParseArgsFromFile(f);
9820                 fseek(f, 0, SEEK_SET);
9821                 FREE(appData.participants); appData.participants = participants;
9822                 if(expunge) { // erase results of replaced engine
9823                     int len = strlen(appData.results), w, b, dummy;
9824                     for(i=0; i<len; i++) {
9825                         Pairing(i, nPlayers, &w, &b, &dummy);
9826                         if((w == changed || b == changed) && appData.results[i] == '*') {
9827                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9828                             fclose(f);
9829                             return;
9830                         }
9831                     }
9832                     for(i=0; i<len; i++) {
9833                         Pairing(i, nPlayers, &w, &b, &dummy);
9834                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9835                     }
9836                 }
9837                 WriteTourneyFile(appData.results, f);
9838                 fclose(f); // release lock
9839                 return;
9840             }
9841         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9842     }
9843     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9844     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9845     free(participants);
9846     return;
9847 }
9848
9849 int
9850 CreateTourney (char *name)
9851 {
9852         FILE *f;
9853         if(matchMode && strcmp(name, appData.tourneyFile)) {
9854              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9855         }
9856         if(name[0] == NULLCHAR) {
9857             if(appData.participants[0])
9858                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9859             return 0;
9860         }
9861         f = fopen(name, "r");
9862         if(f) { // file exists
9863             ASSIGN(appData.tourneyFile, name);
9864             ParseArgsFromFile(f); // parse it
9865         } else {
9866             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9867             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9868                 DisplayError(_("Not enough participants"), 0);
9869                 return 0;
9870             }
9871             ASSIGN(appData.tourneyFile, name);
9872             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9873             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9874         }
9875         fclose(f);
9876         appData.noChessProgram = FALSE;
9877         appData.clockMode = TRUE;
9878         SetGNUMode();
9879         return 1;
9880 }
9881
9882 int
9883 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
9884 {
9885     char buf[MSG_SIZ], *p, *q;
9886     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
9887     skip = !all && group[0]; // if group requested, we start in skip mode
9888     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
9889         p = names; q = buf; header = 0;
9890         while(*p && *p != '\n') *q++ = *p++;
9891         *q = 0;
9892         if(*p == '\n') p++;
9893         if(buf[0] == '#') {
9894             if(strstr(buf, "# end") == buf) { depth--; continue; } // leave group, and suppress printing label
9895             depth++; // we must be entering a new group
9896             if(all) continue; // suppress printing group headers when complete list requested
9897             header = 1;
9898             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
9899         }
9900         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
9901         if(engineList[i]) free(engineList[i]);
9902         engineList[i] = strdup(buf);
9903         if(buf[0] != '#') TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
9904         if(engineMnemonic[i]) free(engineMnemonic[i]);
9905         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9906             strcat(buf, " (");
9907             sscanf(q + 8, "%s", buf + strlen(buf));
9908             strcat(buf, ")");
9909         }
9910         engineMnemonic[i] = strdup(buf);
9911         i++;
9912     }
9913     engineList[i] = engineMnemonic[i] = NULL;
9914     return i;
9915 }
9916
9917 // following implemented as macro to avoid type limitations
9918 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9919
9920 void
9921 SwapEngines (int n)
9922 {   // swap settings for first engine and other engine (so far only some selected options)
9923     int h;
9924     char *p;
9925     if(n == 0) return;
9926     SWAP(directory, p)
9927     SWAP(chessProgram, p)
9928     SWAP(isUCI, h)
9929     SWAP(hasOwnBookUCI, h)
9930     SWAP(protocolVersion, h)
9931     SWAP(reuse, h)
9932     SWAP(scoreIsAbsolute, h)
9933     SWAP(timeOdds, h)
9934     SWAP(logo, p)
9935     SWAP(pgnName, p)
9936     SWAP(pvSAN, h)
9937     SWAP(engOptions, p)
9938 }
9939
9940 int
9941 SetPlayer (int player, char *p)
9942 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9943     int i;
9944     char buf[MSG_SIZ], *engineName;
9945     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9946     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9947     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9948     if(mnemonic[i]) {
9949         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9950         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9951         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
9952         ParseArgsFromString(buf);
9953     }
9954     free(engineName);
9955     return i;
9956 }
9957
9958 char *recentEngines;
9959
9960 void
9961 RecentEngineEvent (int nr)
9962 {
9963     int n;
9964 //    SwapEngines(1); // bump first to second
9965 //    ReplaceEngine(&second, 1); // and load it there
9966     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
9967     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
9968     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
9969         ReplaceEngine(&first, 0);
9970         FloatToFront(&appData.recentEngineList, command[n]);
9971     }
9972 }
9973
9974 int
9975 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9976 {   // determine players from game number
9977     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9978
9979     if(appData.tourneyType == 0) {
9980         roundsPerCycle = (nPlayers - 1) | 1;
9981         pairingsPerRound = nPlayers / 2;
9982     } else if(appData.tourneyType > 0) {
9983         roundsPerCycle = nPlayers - appData.tourneyType;
9984         pairingsPerRound = appData.tourneyType;
9985     }
9986     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9987     gamesPerCycle = gamesPerRound * roundsPerCycle;
9988     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9989     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9990     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9991     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9992     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9993     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9994
9995     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9996     if(appData.roundSync) *syncInterval = gamesPerRound;
9997
9998     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9999
10000     if(appData.tourneyType == 0) {
10001         if(curPairing == (nPlayers-1)/2 ) {
10002             *whitePlayer = curRound;
10003             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10004         } else {
10005             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10006             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10007             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10008             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10009         }
10010     } else if(appData.tourneyType > 1) {
10011         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10012         *whitePlayer = curRound + appData.tourneyType;
10013     } else if(appData.tourneyType > 0) {
10014         *whitePlayer = curPairing;
10015         *blackPlayer = curRound + appData.tourneyType;
10016     }
10017
10018     // take care of white/black alternation per round. 
10019     // For cycles and games this is already taken care of by default, derived from matchGame!
10020     return curRound & 1;
10021 }
10022
10023 int
10024 NextTourneyGame (int nr, int *swapColors)
10025 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10026     char *p, *q;
10027     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10028     FILE *tf;
10029     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10030     tf = fopen(appData.tourneyFile, "r");
10031     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10032     ParseArgsFromFile(tf); fclose(tf);
10033     InitTimeControls(); // TC might be altered from tourney file
10034
10035     nPlayers = CountPlayers(appData.participants); // count participants
10036     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10037     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10038
10039     if(syncInterval) {
10040         p = q = appData.results;
10041         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10042         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10043             DisplayMessage(_("Waiting for other game(s)"),"");
10044             waitingForGame = TRUE;
10045             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10046             return 0;
10047         }
10048         waitingForGame = FALSE;
10049     }
10050
10051     if(appData.tourneyType < 0) {
10052         if(nr>=0 && !pairingReceived) {
10053             char buf[1<<16];
10054             if(pairing.pr == NoProc) {
10055                 if(!appData.pairingEngine[0]) {
10056                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10057                     return 0;
10058                 }
10059                 StartChessProgram(&pairing); // starts the pairing engine
10060             }
10061             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10062             SendToProgram(buf, &pairing);
10063             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10064             SendToProgram(buf, &pairing);
10065             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10066         }
10067         pairingReceived = 0;                              // ... so we continue here 
10068         *swapColors = 0;
10069         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10070         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10071         matchGame = 1; roundNr = nr / syncInterval + 1;
10072     }
10073
10074     if(first.pr != NoProc && second.pr != NoProc) return 1; // engines already loaded
10075
10076     // redefine engines, engine dir, etc.
10077     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10078     if(first.pr == NoProc || nr < 0) {
10079       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10080       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10081     }
10082     if(second.pr == NoProc) {
10083       SwapEngines(1);
10084       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10085       SwapEngines(1);         // and make that valid for second engine by swapping
10086       InitEngine(&second, 1);
10087     }
10088     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10089     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10090     return 1;
10091 }
10092
10093 void
10094 NextMatchGame ()
10095 {   // performs game initialization that does not invoke engines, and then tries to start the game
10096     int res, firstWhite, swapColors = 0;
10097     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10098     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10099     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10100     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10101     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10102     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10103     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10104     Reset(FALSE, first.pr != NoProc);
10105     res = LoadGameOrPosition(matchGame); // setup game
10106     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10107     if(!res) return; // abort when bad game/pos file
10108     TwoMachinesEvent();
10109 }
10110
10111 void
10112 UserAdjudicationEvent (int result)
10113 {
10114     ChessMove gameResult = GameIsDrawn;
10115
10116     if( result > 0 ) {
10117         gameResult = WhiteWins;
10118     }
10119     else if( result < 0 ) {
10120         gameResult = BlackWins;
10121     }
10122
10123     if( gameMode == TwoMachinesPlay ) {
10124         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10125     }
10126 }
10127
10128
10129 // [HGM] save: calculate checksum of game to make games easily identifiable
10130 int
10131 StringCheckSum (char *s)
10132 {
10133         int i = 0;
10134         if(s==NULL) return 0;
10135         while(*s) i = i*259 + *s++;
10136         return i;
10137 }
10138
10139 int
10140 GameCheckSum ()
10141 {
10142         int i, sum=0;
10143         for(i=backwardMostMove; i<forwardMostMove; i++) {
10144                 sum += pvInfoList[i].depth;
10145                 sum += StringCheckSum(parseList[i]);
10146                 sum += StringCheckSum(commentList[i]);
10147                 sum *= 261;
10148         }
10149         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10150         return sum + StringCheckSum(commentList[i]);
10151 } // end of save patch
10152
10153 void
10154 GameEnds (ChessMove result, char *resultDetails, int whosays)
10155 {
10156     GameMode nextGameMode;
10157     int isIcsGame;
10158     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10159
10160     if(endingGame) return; /* [HGM] crash: forbid recursion */
10161     endingGame = 1;
10162     if(twoBoards) { // [HGM] dual: switch back to one board
10163         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10164         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10165     }
10166     if (appData.debugMode) {
10167       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10168               result, resultDetails ? resultDetails : "(null)", whosays);
10169     }
10170
10171     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10172
10173     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10174         /* If we are playing on ICS, the server decides when the
10175            game is over, but the engine can offer to draw, claim
10176            a draw, or resign.
10177          */
10178 #if ZIPPY
10179         if (appData.zippyPlay && first.initDone) {
10180             if (result == GameIsDrawn) {
10181                 /* In case draw still needs to be claimed */
10182                 SendToICS(ics_prefix);
10183                 SendToICS("draw\n");
10184             } else if (StrCaseStr(resultDetails, "resign")) {
10185                 SendToICS(ics_prefix);
10186                 SendToICS("resign\n");
10187             }
10188         }
10189 #endif
10190         endingGame = 0; /* [HGM] crash */
10191         return;
10192     }
10193
10194     /* If we're loading the game from a file, stop */
10195     if (whosays == GE_FILE) {
10196       (void) StopLoadGameTimer();
10197       gameFileFP = NULL;
10198     }
10199
10200     /* Cancel draw offers */
10201     first.offeredDraw = second.offeredDraw = 0;
10202
10203     /* If this is an ICS game, only ICS can really say it's done;
10204        if not, anyone can. */
10205     isIcsGame = (gameMode == IcsPlayingWhite ||
10206                  gameMode == IcsPlayingBlack ||
10207                  gameMode == IcsObserving    ||
10208                  gameMode == IcsExamining);
10209
10210     if (!isIcsGame || whosays == GE_ICS) {
10211         /* OK -- not an ICS game, or ICS said it was done */
10212         StopClocks();
10213         if (!isIcsGame && !appData.noChessProgram)
10214           SetUserThinkingEnables();
10215
10216         /* [HGM] if a machine claims the game end we verify this claim */
10217         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10218             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10219                 char claimer;
10220                 ChessMove trueResult = (ChessMove) -1;
10221
10222                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10223                                             first.twoMachinesColor[0] :
10224                                             second.twoMachinesColor[0] ;
10225
10226                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10227                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10228                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10229                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10230                 } else
10231                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10232                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10233                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10234                 } else
10235                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10236                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10237                 }
10238
10239                 // now verify win claims, but not in drop games, as we don't understand those yet
10240                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10241                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10242                     (result == WhiteWins && claimer == 'w' ||
10243                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10244                       if (appData.debugMode) {
10245                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10246                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10247                       }
10248                       if(result != trueResult) {
10249                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10250                               result = claimer == 'w' ? BlackWins : WhiteWins;
10251                               resultDetails = buf;
10252                       }
10253                 } else
10254                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10255                     && (forwardMostMove <= backwardMostMove ||
10256                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10257                         (claimer=='b')==(forwardMostMove&1))
10258                                                                                   ) {
10259                       /* [HGM] verify: draws that were not flagged are false claims */
10260                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10261                       result = claimer == 'w' ? BlackWins : WhiteWins;
10262                       resultDetails = buf;
10263                 }
10264                 /* (Claiming a loss is accepted no questions asked!) */
10265             }
10266             /* [HGM] bare: don't allow bare King to win */
10267             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10268                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10269                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10270                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10271                && result != GameIsDrawn)
10272             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10273                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10274                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10275                         if(p >= 0 && p <= (int)WhiteKing) k++;
10276                 }
10277                 if (appData.debugMode) {
10278                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10279                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10280                 }
10281                 if(k <= 1) {
10282                         result = GameIsDrawn;
10283                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10284                         resultDetails = buf;
10285                 }
10286             }
10287         }
10288
10289
10290         if(serverMoves != NULL && !loadFlag) { char c = '=';
10291             if(result==WhiteWins) c = '+';
10292             if(result==BlackWins) c = '-';
10293             if(resultDetails != NULL)
10294                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10295         }
10296         if (resultDetails != NULL) {
10297             gameInfo.result = result;
10298             gameInfo.resultDetails = StrSave(resultDetails);
10299
10300             /* display last move only if game was not loaded from file */
10301             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10302                 DisplayMove(currentMove - 1);
10303
10304             if (forwardMostMove != 0) {
10305                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10306                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10307                                                                 ) {
10308                     if (*appData.saveGameFile != NULLCHAR) {
10309                         SaveGameToFile(appData.saveGameFile, TRUE);
10310                     } else if (appData.autoSaveGames) {
10311                         AutoSaveGame();
10312                     }
10313                     if (*appData.savePositionFile != NULLCHAR) {
10314                         SavePositionToFile(appData.savePositionFile);
10315                     }
10316                 }
10317             }
10318
10319             /* Tell program how game ended in case it is learning */
10320             /* [HGM] Moved this to after saving the PGN, just in case */
10321             /* engine died and we got here through time loss. In that */
10322             /* case we will get a fatal error writing the pipe, which */
10323             /* would otherwise lose us the PGN.                       */
10324             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10325             /* output during GameEnds should never be fatal anymore   */
10326             if (gameMode == MachinePlaysWhite ||
10327                 gameMode == MachinePlaysBlack ||
10328                 gameMode == TwoMachinesPlay ||
10329                 gameMode == IcsPlayingWhite ||
10330                 gameMode == IcsPlayingBlack ||
10331                 gameMode == BeginningOfGame) {
10332                 char buf[MSG_SIZ];
10333                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10334                         resultDetails);
10335                 if (first.pr != NoProc) {
10336                     SendToProgram(buf, &first);
10337                 }
10338                 if (second.pr != NoProc &&
10339                     gameMode == TwoMachinesPlay) {
10340                     SendToProgram(buf, &second);
10341                 }
10342             }
10343         }
10344
10345         if (appData.icsActive) {
10346             if (appData.quietPlay &&
10347                 (gameMode == IcsPlayingWhite ||
10348                  gameMode == IcsPlayingBlack)) {
10349                 SendToICS(ics_prefix);
10350                 SendToICS("set shout 1\n");
10351             }
10352             nextGameMode = IcsIdle;
10353             ics_user_moved = FALSE;
10354             /* clean up premove.  It's ugly when the game has ended and the
10355              * premove highlights are still on the board.
10356              */
10357             if (gotPremove) {
10358               gotPremove = FALSE;
10359               ClearPremoveHighlights();
10360               DrawPosition(FALSE, boards[currentMove]);
10361             }
10362             if (whosays == GE_ICS) {
10363                 switch (result) {
10364                 case WhiteWins:
10365                     if (gameMode == IcsPlayingWhite)
10366                         PlayIcsWinSound();
10367                     else if(gameMode == IcsPlayingBlack)
10368                         PlayIcsLossSound();
10369                     break;
10370                 case BlackWins:
10371                     if (gameMode == IcsPlayingBlack)
10372                         PlayIcsWinSound();
10373                     else if(gameMode == IcsPlayingWhite)
10374                         PlayIcsLossSound();
10375                     break;
10376                 case GameIsDrawn:
10377                     PlayIcsDrawSound();
10378                     break;
10379                 default:
10380                     PlayIcsUnfinishedSound();
10381                 }
10382             }
10383         } else if (gameMode == EditGame ||
10384                    gameMode == PlayFromGameFile ||
10385                    gameMode == AnalyzeMode ||
10386                    gameMode == AnalyzeFile) {
10387             nextGameMode = gameMode;
10388         } else {
10389             nextGameMode = EndOfGame;
10390         }
10391         pausing = FALSE;
10392         ModeHighlight();
10393     } else {
10394         nextGameMode = gameMode;
10395     }
10396
10397     if (appData.noChessProgram) {
10398         gameMode = nextGameMode;
10399         ModeHighlight();
10400         endingGame = 0; /* [HGM] crash */
10401         return;
10402     }
10403
10404     if (first.reuse) {
10405         /* Put first chess program into idle state */
10406         if (first.pr != NoProc &&
10407             (gameMode == MachinePlaysWhite ||
10408              gameMode == MachinePlaysBlack ||
10409              gameMode == TwoMachinesPlay ||
10410              gameMode == IcsPlayingWhite ||
10411              gameMode == IcsPlayingBlack ||
10412              gameMode == BeginningOfGame)) {
10413             SendToProgram("force\n", &first);
10414             if (first.usePing) {
10415               char buf[MSG_SIZ];
10416               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10417               SendToProgram(buf, &first);
10418             }
10419         }
10420     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10421         /* Kill off first chess program */
10422         if (first.isr != NULL)
10423           RemoveInputSource(first.isr);
10424         first.isr = NULL;
10425
10426         if (first.pr != NoProc) {
10427             ExitAnalyzeMode();
10428             DoSleep( appData.delayBeforeQuit );
10429             SendToProgram("quit\n", &first);
10430             DoSleep( appData.delayAfterQuit );
10431             DestroyChildProcess(first.pr, first.useSigterm);
10432         }
10433         first.pr = NoProc;
10434     }
10435     if (second.reuse) {
10436         /* Put second chess program into idle state */
10437         if (second.pr != NoProc &&
10438             gameMode == TwoMachinesPlay) {
10439             SendToProgram("force\n", &second);
10440             if (second.usePing) {
10441               char buf[MSG_SIZ];
10442               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10443               SendToProgram(buf, &second);
10444             }
10445         }
10446     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10447         /* Kill off second chess program */
10448         if (second.isr != NULL)
10449           RemoveInputSource(second.isr);
10450         second.isr = NULL;
10451
10452         if (second.pr != NoProc) {
10453             DoSleep( appData.delayBeforeQuit );
10454             SendToProgram("quit\n", &second);
10455             DoSleep( appData.delayAfterQuit );
10456             DestroyChildProcess(second.pr, second.useSigterm);
10457         }
10458         second.pr = NoProc;
10459     }
10460
10461     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10462         char resChar = '=';
10463         switch (result) {
10464         case WhiteWins:
10465           resChar = '+';
10466           if (first.twoMachinesColor[0] == 'w') {
10467             first.matchWins++;
10468           } else {
10469             second.matchWins++;
10470           }
10471           break;
10472         case BlackWins:
10473           resChar = '-';
10474           if (first.twoMachinesColor[0] == 'b') {
10475             first.matchWins++;
10476           } else {
10477             second.matchWins++;
10478           }
10479           break;
10480         case GameUnfinished:
10481           resChar = ' ';
10482         default:
10483           break;
10484         }
10485
10486         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10487         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10488             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10489             ReserveGame(nextGame, resChar); // sets nextGame
10490             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10491             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10492         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10493
10494         if (nextGame <= appData.matchGames && !abortMatch) {
10495             gameMode = nextGameMode;
10496             matchGame = nextGame; // this will be overruled in tourney mode!
10497             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10498             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10499             endingGame = 0; /* [HGM] crash */
10500             return;
10501         } else {
10502             gameMode = nextGameMode;
10503             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10504                      first.tidy, second.tidy,
10505                      first.matchWins, second.matchWins,
10506                      appData.matchGames - (first.matchWins + second.matchWins));
10507             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10508             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10509             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10510             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10511                 first.twoMachinesColor = "black\n";
10512                 second.twoMachinesColor = "white\n";
10513             } else {
10514                 first.twoMachinesColor = "white\n";
10515                 second.twoMachinesColor = "black\n";
10516             }
10517         }
10518     }
10519     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10520         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10521       ExitAnalyzeMode();
10522     gameMode = nextGameMode;
10523     ModeHighlight();
10524     endingGame = 0;  /* [HGM] crash */
10525     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10526         if(matchMode == TRUE) { // match through command line: exit with or without popup
10527             if(ranking) {
10528                 ToNrEvent(forwardMostMove);
10529                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10530                 else ExitEvent(0);
10531             } else DisplayFatalError(buf, 0, 0);
10532         } else { // match through menu; just stop, with or without popup
10533             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10534             ModeHighlight();
10535             if(ranking){
10536                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10537             } else DisplayNote(buf);
10538       }
10539       if(ranking) free(ranking);
10540     }
10541 }
10542
10543 /* Assumes program was just initialized (initString sent).
10544    Leaves program in force mode. */
10545 void
10546 FeedMovesToProgram (ChessProgramState *cps, int upto)
10547 {
10548     int i;
10549
10550     if (appData.debugMode)
10551       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10552               startedFromSetupPosition ? "position and " : "",
10553               backwardMostMove, upto, cps->which);
10554     if(currentlyInitializedVariant != gameInfo.variant) {
10555       char buf[MSG_SIZ];
10556         // [HGM] variantswitch: make engine aware of new variant
10557         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10558                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10559         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10560         SendToProgram(buf, cps);
10561         currentlyInitializedVariant = gameInfo.variant;
10562     }
10563     SendToProgram("force\n", cps);
10564     if (startedFromSetupPosition) {
10565         SendBoard(cps, backwardMostMove);
10566     if (appData.debugMode) {
10567         fprintf(debugFP, "feedMoves\n");
10568     }
10569     }
10570     for (i = backwardMostMove; i < upto; i++) {
10571         SendMoveToProgram(i, cps);
10572     }
10573 }
10574
10575
10576 int
10577 ResurrectChessProgram ()
10578 {
10579      /* The chess program may have exited.
10580         If so, restart it and feed it all the moves made so far. */
10581     static int doInit = 0;
10582
10583     if (appData.noChessProgram) return 1;
10584
10585     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10586         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10587         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10588         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10589     } else {
10590         if (first.pr != NoProc) return 1;
10591         StartChessProgram(&first);
10592     }
10593     InitChessProgram(&first, FALSE);
10594     FeedMovesToProgram(&first, currentMove);
10595
10596     if (!first.sendTime) {
10597         /* can't tell gnuchess what its clock should read,
10598            so we bow to its notion. */
10599         ResetClocks();
10600         timeRemaining[0][currentMove] = whiteTimeRemaining;
10601         timeRemaining[1][currentMove] = blackTimeRemaining;
10602     }
10603
10604     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10605                 appData.icsEngineAnalyze) && first.analysisSupport) {
10606       SendToProgram("analyze\n", &first);
10607       first.analyzing = TRUE;
10608     }
10609     return 1;
10610 }
10611
10612 /*
10613  * Button procedures
10614  */
10615 void
10616 Reset (int redraw, int init)
10617 {
10618     int i;
10619
10620     if (appData.debugMode) {
10621         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10622                 redraw, init, gameMode);
10623     }
10624     CleanupTail(); // [HGM] vari: delete any stored variations
10625     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10626     pausing = pauseExamInvalid = FALSE;
10627     startedFromSetupPosition = blackPlaysFirst = FALSE;
10628     firstMove = TRUE;
10629     whiteFlag = blackFlag = FALSE;
10630     userOfferedDraw = FALSE;
10631     hintRequested = bookRequested = FALSE;
10632     first.maybeThinking = FALSE;
10633     second.maybeThinking = FALSE;
10634     first.bookSuspend = FALSE; // [HGM] book
10635     second.bookSuspend = FALSE;
10636     thinkOutput[0] = NULLCHAR;
10637     lastHint[0] = NULLCHAR;
10638     ClearGameInfo(&gameInfo);
10639     gameInfo.variant = StringToVariant(appData.variant);
10640     ics_user_moved = ics_clock_paused = FALSE;
10641     ics_getting_history = H_FALSE;
10642     ics_gamenum = -1;
10643     white_holding[0] = black_holding[0] = NULLCHAR;
10644     ClearProgramStats();
10645     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10646
10647     ResetFrontEnd();
10648     ClearHighlights();
10649     flipView = appData.flipView;
10650     ClearPremoveHighlights();
10651     gotPremove = FALSE;
10652     alarmSounded = FALSE;
10653
10654     GameEnds(EndOfFile, NULL, GE_PLAYER);
10655     if(appData.serverMovesName != NULL) {
10656         /* [HGM] prepare to make moves file for broadcasting */
10657         clock_t t = clock();
10658         if(serverMoves != NULL) fclose(serverMoves);
10659         serverMoves = fopen(appData.serverMovesName, "r");
10660         if(serverMoves != NULL) {
10661             fclose(serverMoves);
10662             /* delay 15 sec before overwriting, so all clients can see end */
10663             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10664         }
10665         serverMoves = fopen(appData.serverMovesName, "w");
10666     }
10667
10668     ExitAnalyzeMode();
10669     gameMode = BeginningOfGame;
10670     ModeHighlight();
10671     if(appData.icsActive) gameInfo.variant = VariantNormal;
10672     currentMove = forwardMostMove = backwardMostMove = 0;
10673     MarkTargetSquares(1);
10674     InitPosition(redraw);
10675     for (i = 0; i < MAX_MOVES; i++) {
10676         if (commentList[i] != NULL) {
10677             free(commentList[i]);
10678             commentList[i] = NULL;
10679         }
10680     }
10681     ResetClocks();
10682     timeRemaining[0][0] = whiteTimeRemaining;
10683     timeRemaining[1][0] = blackTimeRemaining;
10684
10685     if (first.pr == NoProc) {
10686         StartChessProgram(&first);
10687     }
10688     if (init) {
10689             InitChessProgram(&first, startedFromSetupPosition);
10690     }
10691     DisplayTitle("");
10692     DisplayMessage("", "");
10693     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10694     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10695 }
10696
10697 void
10698 AutoPlayGameLoop ()
10699 {
10700     for (;;) {
10701         if (!AutoPlayOneMove())
10702           return;
10703         if (matchMode || appData.timeDelay == 0)
10704           continue;
10705         if (appData.timeDelay < 0)
10706           return;
10707         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10708         break;
10709     }
10710 }
10711
10712
10713 int
10714 AutoPlayOneMove ()
10715 {
10716     int fromX, fromY, toX, toY;
10717
10718     if (appData.debugMode) {
10719       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10720     }
10721
10722     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10723       return FALSE;
10724
10725     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10726       pvInfoList[currentMove].depth = programStats.depth;
10727       pvInfoList[currentMove].score = programStats.score;
10728       pvInfoList[currentMove].time  = 0;
10729       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10730     }
10731
10732     if (currentMove >= forwardMostMove) {
10733       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10734 //      gameMode = EndOfGame;
10735 //      ModeHighlight();
10736
10737       /* [AS] Clear current move marker at the end of a game */
10738       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10739
10740       return FALSE;
10741     }
10742
10743     toX = moveList[currentMove][2] - AAA;
10744     toY = moveList[currentMove][3] - ONE;
10745
10746     if (moveList[currentMove][1] == '@') {
10747         if (appData.highlightLastMove) {
10748             SetHighlights(-1, -1, toX, toY);
10749         }
10750     } else {
10751         fromX = moveList[currentMove][0] - AAA;
10752         fromY = moveList[currentMove][1] - ONE;
10753
10754         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10755
10756         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10757
10758         if (appData.highlightLastMove) {
10759             SetHighlights(fromX, fromY, toX, toY);
10760         }
10761     }
10762     DisplayMove(currentMove);
10763     SendMoveToProgram(currentMove++, &first);
10764     DisplayBothClocks();
10765     DrawPosition(FALSE, boards[currentMove]);
10766     // [HGM] PV info: always display, routine tests if empty
10767     DisplayComment(currentMove - 1, commentList[currentMove]);
10768     return TRUE;
10769 }
10770
10771
10772 int
10773 LoadGameOneMove (ChessMove readAhead)
10774 {
10775     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10776     char promoChar = NULLCHAR;
10777     ChessMove moveType;
10778     char move[MSG_SIZ];
10779     char *p, *q;
10780
10781     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10782         gameMode != AnalyzeMode && gameMode != Training) {
10783         gameFileFP = NULL;
10784         return FALSE;
10785     }
10786
10787     yyboardindex = forwardMostMove;
10788     if (readAhead != EndOfFile) {
10789       moveType = readAhead;
10790     } else {
10791       if (gameFileFP == NULL)
10792           return FALSE;
10793       moveType = (ChessMove) Myylex();
10794     }
10795
10796     done = FALSE;
10797     switch (moveType) {
10798       case Comment:
10799         if (appData.debugMode)
10800           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10801         p = yy_text;
10802
10803         /* append the comment but don't display it */
10804         AppendComment(currentMove, p, FALSE);
10805         return TRUE;
10806
10807       case WhiteCapturesEnPassant:
10808       case BlackCapturesEnPassant:
10809       case WhitePromotion:
10810       case BlackPromotion:
10811       case WhiteNonPromotion:
10812       case BlackNonPromotion:
10813       case NormalMove:
10814       case WhiteKingSideCastle:
10815       case WhiteQueenSideCastle:
10816       case BlackKingSideCastle:
10817       case BlackQueenSideCastle:
10818       case WhiteKingSideCastleWild:
10819       case WhiteQueenSideCastleWild:
10820       case BlackKingSideCastleWild:
10821       case BlackQueenSideCastleWild:
10822       /* PUSH Fabien */
10823       case WhiteHSideCastleFR:
10824       case WhiteASideCastleFR:
10825       case BlackHSideCastleFR:
10826       case BlackASideCastleFR:
10827       /* POP Fabien */
10828         if (appData.debugMode)
10829           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10830         fromX = currentMoveString[0] - AAA;
10831         fromY = currentMoveString[1] - ONE;
10832         toX = currentMoveString[2] - AAA;
10833         toY = currentMoveString[3] - ONE;
10834         promoChar = currentMoveString[4];
10835         break;
10836
10837       case WhiteDrop:
10838       case BlackDrop:
10839         if (appData.debugMode)
10840           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10841         fromX = moveType == WhiteDrop ?
10842           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10843         (int) CharToPiece(ToLower(currentMoveString[0]));
10844         fromY = DROP_RANK;
10845         toX = currentMoveString[2] - AAA;
10846         toY = currentMoveString[3] - ONE;
10847         break;
10848
10849       case WhiteWins:
10850       case BlackWins:
10851       case GameIsDrawn:
10852       case GameUnfinished:
10853         if (appData.debugMode)
10854           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10855         p = strchr(yy_text, '{');
10856         if (p == NULL) p = strchr(yy_text, '(');
10857         if (p == NULL) {
10858             p = yy_text;
10859             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10860         } else {
10861             q = strchr(p, *p == '{' ? '}' : ')');
10862             if (q != NULL) *q = NULLCHAR;
10863             p++;
10864         }
10865         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10866         GameEnds(moveType, p, GE_FILE);
10867         done = TRUE;
10868         if (cmailMsgLoaded) {
10869             ClearHighlights();
10870             flipView = WhiteOnMove(currentMove);
10871             if (moveType == GameUnfinished) flipView = !flipView;
10872             if (appData.debugMode)
10873               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10874         }
10875         break;
10876
10877       case EndOfFile:
10878         if (appData.debugMode)
10879           fprintf(debugFP, "Parser hit end of file\n");
10880         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10881           case MT_NONE:
10882           case MT_CHECK:
10883             break;
10884           case MT_CHECKMATE:
10885           case MT_STAINMATE:
10886             if (WhiteOnMove(currentMove)) {
10887                 GameEnds(BlackWins, "Black mates", GE_FILE);
10888             } else {
10889                 GameEnds(WhiteWins, "White mates", GE_FILE);
10890             }
10891             break;
10892           case MT_STALEMATE:
10893             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10894             break;
10895         }
10896         done = TRUE;
10897         break;
10898
10899       case MoveNumberOne:
10900         if (lastLoadGameStart == GNUChessGame) {
10901             /* GNUChessGames have numbers, but they aren't move numbers */
10902             if (appData.debugMode)
10903               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10904                       yy_text, (int) moveType);
10905             return LoadGameOneMove(EndOfFile); /* tail recursion */
10906         }
10907         /* else fall thru */
10908
10909       case XBoardGame:
10910       case GNUChessGame:
10911       case PGNTag:
10912         /* Reached start of next game in file */
10913         if (appData.debugMode)
10914           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10915         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10916           case MT_NONE:
10917           case MT_CHECK:
10918             break;
10919           case MT_CHECKMATE:
10920           case MT_STAINMATE:
10921             if (WhiteOnMove(currentMove)) {
10922                 GameEnds(BlackWins, "Black mates", GE_FILE);
10923             } else {
10924                 GameEnds(WhiteWins, "White mates", GE_FILE);
10925             }
10926             break;
10927           case MT_STALEMATE:
10928             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10929             break;
10930         }
10931         done = TRUE;
10932         break;
10933
10934       case PositionDiagram:     /* should not happen; ignore */
10935       case ElapsedTime:         /* ignore */
10936       case NAG:                 /* ignore */
10937         if (appData.debugMode)
10938           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10939                   yy_text, (int) moveType);
10940         return LoadGameOneMove(EndOfFile); /* tail recursion */
10941
10942       case IllegalMove:
10943         if (appData.testLegality) {
10944             if (appData.debugMode)
10945               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10946             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10947                     (forwardMostMove / 2) + 1,
10948                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10949             DisplayError(move, 0);
10950             done = TRUE;
10951         } else {
10952             if (appData.debugMode)
10953               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10954                       yy_text, currentMoveString);
10955             fromX = currentMoveString[0] - AAA;
10956             fromY = currentMoveString[1] - ONE;
10957             toX = currentMoveString[2] - AAA;
10958             toY = currentMoveString[3] - ONE;
10959             promoChar = currentMoveString[4];
10960         }
10961         break;
10962
10963       case AmbiguousMove:
10964         if (appData.debugMode)
10965           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10966         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10967                 (forwardMostMove / 2) + 1,
10968                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10969         DisplayError(move, 0);
10970         done = TRUE;
10971         break;
10972
10973       default:
10974       case ImpossibleMove:
10975         if (appData.debugMode)
10976           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10977         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10978                 (forwardMostMove / 2) + 1,
10979                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10980         DisplayError(move, 0);
10981         done = TRUE;
10982         break;
10983     }
10984
10985     if (done) {
10986         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10987             DrawPosition(FALSE, boards[currentMove]);
10988             DisplayBothClocks();
10989             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10990               DisplayComment(currentMove - 1, commentList[currentMove]);
10991         }
10992         (void) StopLoadGameTimer();
10993         gameFileFP = NULL;
10994         cmailOldMove = forwardMostMove;
10995         return FALSE;
10996     } else {
10997         /* currentMoveString is set as a side-effect of yylex */
10998
10999         thinkOutput[0] = NULLCHAR;
11000         MakeMove(fromX, fromY, toX, toY, promoChar);
11001         currentMove = forwardMostMove;
11002         return TRUE;
11003     }
11004 }
11005
11006 /* Load the nth game from the given file */
11007 int
11008 LoadGameFromFile (char *filename, int n, char *title, int useList)
11009 {
11010     FILE *f;
11011     char buf[MSG_SIZ];
11012
11013     if (strcmp(filename, "-") == 0) {
11014         f = stdin;
11015         title = "stdin";
11016     } else {
11017         f = fopen(filename, "rb");
11018         if (f == NULL) {
11019           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11020             DisplayError(buf, errno);
11021             return FALSE;
11022         }
11023     }
11024     if (fseek(f, 0, 0) == -1) {
11025         /* f is not seekable; probably a pipe */
11026         useList = FALSE;
11027     }
11028     if (useList && n == 0) {
11029         int error = GameListBuild(f);
11030         if (error) {
11031             DisplayError(_("Cannot build game list"), error);
11032         } else if (!ListEmpty(&gameList) &&
11033                    ((ListGame *) gameList.tailPred)->number > 1) {
11034             GameListPopUp(f, title);
11035             return TRUE;
11036         }
11037         GameListDestroy();
11038         n = 1;
11039     }
11040     if (n == 0) n = 1;
11041     return LoadGame(f, n, title, FALSE);
11042 }
11043
11044
11045 void
11046 MakeRegisteredMove ()
11047 {
11048     int fromX, fromY, toX, toY;
11049     char promoChar;
11050     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11051         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11052           case CMAIL_MOVE:
11053           case CMAIL_DRAW:
11054             if (appData.debugMode)
11055               fprintf(debugFP, "Restoring %s for game %d\n",
11056                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11057
11058             thinkOutput[0] = NULLCHAR;
11059             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11060             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11061             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11062             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11063             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11064             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11065             MakeMove(fromX, fromY, toX, toY, promoChar);
11066             ShowMove(fromX, fromY, toX, toY);
11067
11068             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11069               case MT_NONE:
11070               case MT_CHECK:
11071                 break;
11072
11073               case MT_CHECKMATE:
11074               case MT_STAINMATE:
11075                 if (WhiteOnMove(currentMove)) {
11076                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11077                 } else {
11078                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11079                 }
11080                 break;
11081
11082               case MT_STALEMATE:
11083                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11084                 break;
11085             }
11086
11087             break;
11088
11089           case CMAIL_RESIGN:
11090             if (WhiteOnMove(currentMove)) {
11091                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11092             } else {
11093                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11094             }
11095             break;
11096
11097           case CMAIL_ACCEPT:
11098             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11099             break;
11100
11101           default:
11102             break;
11103         }
11104     }
11105
11106     return;
11107 }
11108
11109 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11110 int
11111 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11112 {
11113     int retVal;
11114
11115     if (gameNumber > nCmailGames) {
11116         DisplayError(_("No more games in this message"), 0);
11117         return FALSE;
11118     }
11119     if (f == lastLoadGameFP) {
11120         int offset = gameNumber - lastLoadGameNumber;
11121         if (offset == 0) {
11122             cmailMsg[0] = NULLCHAR;
11123             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11124                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11125                 nCmailMovesRegistered--;
11126             }
11127             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11128             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11129                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11130             }
11131         } else {
11132             if (! RegisterMove()) return FALSE;
11133         }
11134     }
11135
11136     retVal = LoadGame(f, gameNumber, title, useList);
11137
11138     /* Make move registered during previous look at this game, if any */
11139     MakeRegisteredMove();
11140
11141     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11142         commentList[currentMove]
11143           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11144         DisplayComment(currentMove - 1, commentList[currentMove]);
11145     }
11146
11147     return retVal;
11148 }
11149
11150 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11151 int
11152 ReloadGame (int offset)
11153 {
11154     int gameNumber = lastLoadGameNumber + offset;
11155     if (lastLoadGameFP == NULL) {
11156         DisplayError(_("No game has been loaded yet"), 0);
11157         return FALSE;
11158     }
11159     if (gameNumber <= 0) {
11160         DisplayError(_("Can't back up any further"), 0);
11161         return FALSE;
11162     }
11163     if (cmailMsgLoaded) {
11164         return CmailLoadGame(lastLoadGameFP, gameNumber,
11165                              lastLoadGameTitle, lastLoadGameUseList);
11166     } else {
11167         return LoadGame(lastLoadGameFP, gameNumber,
11168                         lastLoadGameTitle, lastLoadGameUseList);
11169     }
11170 }
11171
11172 int keys[EmptySquare+1];
11173
11174 int
11175 PositionMatches (Board b1, Board b2)
11176 {
11177     int r, f, sum=0;
11178     switch(appData.searchMode) {
11179         case 1: return CompareWithRights(b1, b2);
11180         case 2:
11181             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11182                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11183             }
11184             return TRUE;
11185         case 3:
11186             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11187               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11188                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11189             }
11190             return sum==0;
11191         case 4:
11192             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11193                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11194             }
11195             return sum==0;
11196     }
11197     return TRUE;
11198 }
11199
11200 #define Q_PROMO  4
11201 #define Q_EP     3
11202 #define Q_BCASTL 2
11203 #define Q_WCASTL 1
11204
11205 int pieceList[256], quickBoard[256];
11206 ChessSquare pieceType[256] = { EmptySquare };
11207 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11208 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11209 int soughtTotal, turn;
11210 Boolean epOK, flipSearch;
11211
11212 typedef struct {
11213     unsigned char piece, to;
11214 } Move;
11215
11216 #define DSIZE (250000)
11217
11218 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11219 Move *moveDatabase = initialSpace;
11220 unsigned int movePtr, dataSize = DSIZE;
11221
11222 int
11223 MakePieceList (Board board, int *counts)
11224 {
11225     int r, f, n=Q_PROMO, total=0;
11226     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11227     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11228         int sq = f + (r<<4);
11229         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11230             quickBoard[sq] = ++n;
11231             pieceList[n] = sq;
11232             pieceType[n] = board[r][f];
11233             counts[board[r][f]]++;
11234             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11235             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11236             total++;
11237         }
11238     }
11239     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11240     return total;
11241 }
11242
11243 void
11244 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11245 {
11246     int sq = fromX + (fromY<<4);
11247     int piece = quickBoard[sq];
11248     quickBoard[sq] = 0;
11249     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11250     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11251         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11252         moveDatabase[movePtr++].piece = Q_WCASTL;
11253         quickBoard[sq] = piece;
11254         piece = quickBoard[from]; quickBoard[from] = 0;
11255         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11256     } else
11257     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11258         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11259         moveDatabase[movePtr++].piece = Q_BCASTL;
11260         quickBoard[sq] = piece;
11261         piece = quickBoard[from]; quickBoard[from] = 0;
11262         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11263     } else
11264     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11265         quickBoard[(fromY<<4)+toX] = 0;
11266         moveDatabase[movePtr].piece = Q_EP;
11267         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11268         moveDatabase[movePtr].to = sq;
11269     } else
11270     if(promoPiece != pieceType[piece]) {
11271         moveDatabase[movePtr++].piece = Q_PROMO;
11272         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11273     }
11274     moveDatabase[movePtr].piece = piece;
11275     quickBoard[sq] = piece;
11276     movePtr++;
11277 }
11278
11279 int
11280 PackGame (Board board)
11281 {
11282     Move *newSpace = NULL;
11283     moveDatabase[movePtr].piece = 0; // terminate previous game
11284     if(movePtr > dataSize) {
11285         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11286         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11287         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11288         if(newSpace) {
11289             int i;
11290             Move *p = moveDatabase, *q = newSpace;
11291             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11292             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11293             moveDatabase = newSpace;
11294         } else { // calloc failed, we must be out of memory. Too bad...
11295             dataSize = 0; // prevent calloc events for all subsequent games
11296             return 0;     // and signal this one isn't cached
11297         }
11298     }
11299     movePtr++;
11300     MakePieceList(board, counts);
11301     return movePtr;
11302 }
11303
11304 int
11305 QuickCompare (Board board, int *minCounts, int *maxCounts)
11306 {   // compare according to search mode
11307     int r, f;
11308     switch(appData.searchMode)
11309     {
11310       case 1: // exact position match
11311         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11312         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11313             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11314         }
11315         break;
11316       case 2: // can have extra material on empty squares
11317         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11318             if(board[r][f] == EmptySquare) continue;
11319             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11320         }
11321         break;
11322       case 3: // material with exact Pawn structure
11323         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11324             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11325             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11326         } // fall through to material comparison
11327       case 4: // exact material
11328         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11329         break;
11330       case 6: // material range with given imbalance
11331         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11332         // fall through to range comparison
11333       case 5: // material range
11334         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11335     }
11336     return TRUE;
11337 }
11338
11339 int
11340 QuickScan (Board board, Move *move)
11341 {   // reconstruct game,and compare all positions in it
11342     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11343     do {
11344         int piece = move->piece;
11345         int to = move->to, from = pieceList[piece];
11346         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11347           if(!piece) return -1;
11348           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11349             piece = (++move)->piece;
11350             from = pieceList[piece];
11351             counts[pieceType[piece]]--;
11352             pieceType[piece] = (ChessSquare) move->to;
11353             counts[move->to]++;
11354           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11355             counts[pieceType[quickBoard[to]]]--;
11356             quickBoard[to] = 0; total--;
11357             move++;
11358             continue;
11359           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11360             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11361             from  = pieceList[piece]; // so this must be King
11362             quickBoard[from] = 0;
11363             quickBoard[to] = piece;
11364             pieceList[piece] = to;
11365             move++;
11366             continue;
11367           }
11368         }
11369         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11370         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11371         quickBoard[from] = 0;
11372         quickBoard[to] = piece;
11373         pieceList[piece] = to;
11374         cnt++; turn ^= 3;
11375         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11376            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11377            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11378                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11379           ) {
11380             static int lastCounts[EmptySquare+1];
11381             int i;
11382             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11383             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11384         } else stretch = 0;
11385         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11386         move++;
11387     } while(1);
11388 }
11389
11390 void
11391 InitSearch ()
11392 {
11393     int r, f;
11394     flipSearch = FALSE;
11395     CopyBoard(soughtBoard, boards[currentMove]);
11396     soughtTotal = MakePieceList(soughtBoard, maxSought);
11397     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11398     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11399     CopyBoard(reverseBoard, boards[currentMove]);
11400     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11401         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11402         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11403         reverseBoard[r][f] = piece;
11404     }
11405     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11406     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11407     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11408                  || (boards[currentMove][CASTLING][2] == NoRights || 
11409                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11410                  && (boards[currentMove][CASTLING][5] == NoRights || 
11411                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11412       ) {
11413         flipSearch = TRUE;
11414         CopyBoard(flipBoard, soughtBoard);
11415         CopyBoard(rotateBoard, reverseBoard);
11416         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11417             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11418             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11419         }
11420     }
11421     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11422     if(appData.searchMode >= 5) {
11423         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11424         MakePieceList(soughtBoard, minSought);
11425         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11426     }
11427     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11428         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11429 }
11430
11431 GameInfo dummyInfo;
11432
11433 int
11434 GameContainsPosition (FILE *f, ListGame *lg)
11435 {
11436     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11437     int fromX, fromY, toX, toY;
11438     char promoChar;
11439     static int initDone=FALSE;
11440
11441     // weed out games based on numerical tag comparison
11442     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11443     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11444     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11445     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11446     if(!initDone) {
11447         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11448         initDone = TRUE;
11449     }
11450     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11451     else CopyBoard(boards[scratch], initialPosition); // default start position
11452     if(lg->moves) {
11453         turn = btm + 1;
11454         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11455         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11456     }
11457     if(btm) plyNr++;
11458     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11459     fseek(f, lg->offset, 0);
11460     yynewfile(f);
11461     while(1) {
11462         yyboardindex = scratch;
11463         quickFlag = plyNr+1;
11464         next = Myylex();
11465         quickFlag = 0;
11466         switch(next) {
11467             case PGNTag:
11468                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11469             default:
11470                 continue;
11471
11472             case XBoardGame:
11473             case GNUChessGame:
11474                 if(plyNr) return -1; // after we have seen moves, this is for new game
11475               continue;
11476
11477             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11478             case ImpossibleMove:
11479             case WhiteWins: // game ends here with these four
11480             case BlackWins:
11481             case GameIsDrawn:
11482             case GameUnfinished:
11483                 return -1;
11484
11485             case IllegalMove:
11486                 if(appData.testLegality) return -1;
11487             case WhiteCapturesEnPassant:
11488             case BlackCapturesEnPassant:
11489             case WhitePromotion:
11490             case BlackPromotion:
11491             case WhiteNonPromotion:
11492             case BlackNonPromotion:
11493             case NormalMove:
11494             case WhiteKingSideCastle:
11495             case WhiteQueenSideCastle:
11496             case BlackKingSideCastle:
11497             case BlackQueenSideCastle:
11498             case WhiteKingSideCastleWild:
11499             case WhiteQueenSideCastleWild:
11500             case BlackKingSideCastleWild:
11501             case BlackQueenSideCastleWild:
11502             case WhiteHSideCastleFR:
11503             case WhiteASideCastleFR:
11504             case BlackHSideCastleFR:
11505             case BlackASideCastleFR:
11506                 fromX = currentMoveString[0] - AAA;
11507                 fromY = currentMoveString[1] - ONE;
11508                 toX = currentMoveString[2] - AAA;
11509                 toY = currentMoveString[3] - ONE;
11510                 promoChar = currentMoveString[4];
11511                 break;
11512             case WhiteDrop:
11513             case BlackDrop:
11514                 fromX = next == WhiteDrop ?
11515                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11516                   (int) CharToPiece(ToLower(currentMoveString[0]));
11517                 fromY = DROP_RANK;
11518                 toX = currentMoveString[2] - AAA;
11519                 toY = currentMoveString[3] - ONE;
11520                 promoChar = 0;
11521                 break;
11522         }
11523         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11524         plyNr++;
11525         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11526         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11527         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11528         if(appData.findMirror) {
11529             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11530             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11531         }
11532     }
11533 }
11534
11535 /* Load the nth game from open file f */
11536 int
11537 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11538 {
11539     ChessMove cm;
11540     char buf[MSG_SIZ];
11541     int gn = gameNumber;
11542     ListGame *lg = NULL;
11543     int numPGNTags = 0;
11544     int err, pos = -1;
11545     GameMode oldGameMode;
11546     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11547
11548     if (appData.debugMode)
11549         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11550
11551     if (gameMode == Training )
11552         SetTrainingModeOff();
11553
11554     oldGameMode = gameMode;
11555     if (gameMode != BeginningOfGame) {
11556       Reset(FALSE, TRUE);
11557     }
11558
11559     gameFileFP = f;
11560     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11561         fclose(lastLoadGameFP);
11562     }
11563
11564     if (useList) {
11565         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11566
11567         if (lg) {
11568             fseek(f, lg->offset, 0);
11569             GameListHighlight(gameNumber);
11570             pos = lg->position;
11571             gn = 1;
11572         }
11573         else {
11574             DisplayError(_("Game number out of range"), 0);
11575             return FALSE;
11576         }
11577     } else {
11578         GameListDestroy();
11579         if (fseek(f, 0, 0) == -1) {
11580             if (f == lastLoadGameFP ?
11581                 gameNumber == lastLoadGameNumber + 1 :
11582                 gameNumber == 1) {
11583                 gn = 1;
11584             } else {
11585                 DisplayError(_("Can't seek on game file"), 0);
11586                 return FALSE;
11587             }
11588         }
11589     }
11590     lastLoadGameFP = f;
11591     lastLoadGameNumber = gameNumber;
11592     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11593     lastLoadGameUseList = useList;
11594
11595     yynewfile(f);
11596
11597     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11598       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11599                 lg->gameInfo.black);
11600             DisplayTitle(buf);
11601     } else if (*title != NULLCHAR) {
11602         if (gameNumber > 1) {
11603           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11604             DisplayTitle(buf);
11605         } else {
11606             DisplayTitle(title);
11607         }
11608     }
11609
11610     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11611         gameMode = PlayFromGameFile;
11612         ModeHighlight();
11613     }
11614
11615     currentMove = forwardMostMove = backwardMostMove = 0;
11616     CopyBoard(boards[0], initialPosition);
11617     StopClocks();
11618
11619     /*
11620      * Skip the first gn-1 games in the file.
11621      * Also skip over anything that precedes an identifiable
11622      * start of game marker, to avoid being confused by
11623      * garbage at the start of the file.  Currently
11624      * recognized start of game markers are the move number "1",
11625      * the pattern "gnuchess .* game", the pattern
11626      * "^[#;%] [^ ]* game file", and a PGN tag block.
11627      * A game that starts with one of the latter two patterns
11628      * will also have a move number 1, possibly
11629      * following a position diagram.
11630      * 5-4-02: Let's try being more lenient and allowing a game to
11631      * start with an unnumbered move.  Does that break anything?
11632      */
11633     cm = lastLoadGameStart = EndOfFile;
11634     while (gn > 0) {
11635         yyboardindex = forwardMostMove;
11636         cm = (ChessMove) Myylex();
11637         switch (cm) {
11638           case EndOfFile:
11639             if (cmailMsgLoaded) {
11640                 nCmailGames = CMAIL_MAX_GAMES - gn;
11641             } else {
11642                 Reset(TRUE, TRUE);
11643                 DisplayError(_("Game not found in file"), 0);
11644             }
11645             return FALSE;
11646
11647           case GNUChessGame:
11648           case XBoardGame:
11649             gn--;
11650             lastLoadGameStart = cm;
11651             break;
11652
11653           case MoveNumberOne:
11654             switch (lastLoadGameStart) {
11655               case GNUChessGame:
11656               case XBoardGame:
11657               case PGNTag:
11658                 break;
11659               case MoveNumberOne:
11660               case EndOfFile:
11661                 gn--;           /* count this game */
11662                 lastLoadGameStart = cm;
11663                 break;
11664               default:
11665                 /* impossible */
11666                 break;
11667             }
11668             break;
11669
11670           case PGNTag:
11671             switch (lastLoadGameStart) {
11672               case GNUChessGame:
11673               case PGNTag:
11674               case MoveNumberOne:
11675               case EndOfFile:
11676                 gn--;           /* count this game */
11677                 lastLoadGameStart = cm;
11678                 break;
11679               case XBoardGame:
11680                 lastLoadGameStart = cm; /* game counted already */
11681                 break;
11682               default:
11683                 /* impossible */
11684                 break;
11685             }
11686             if (gn > 0) {
11687                 do {
11688                     yyboardindex = forwardMostMove;
11689                     cm = (ChessMove) Myylex();
11690                 } while (cm == PGNTag || cm == Comment);
11691             }
11692             break;
11693
11694           case WhiteWins:
11695           case BlackWins:
11696           case GameIsDrawn:
11697             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11698                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11699                     != CMAIL_OLD_RESULT) {
11700                     nCmailResults ++ ;
11701                     cmailResult[  CMAIL_MAX_GAMES
11702                                 - gn - 1] = CMAIL_OLD_RESULT;
11703                 }
11704             }
11705             break;
11706
11707           case NormalMove:
11708             /* Only a NormalMove can be at the start of a game
11709              * without a position diagram. */
11710             if (lastLoadGameStart == EndOfFile ) {
11711               gn--;
11712               lastLoadGameStart = MoveNumberOne;
11713             }
11714             break;
11715
11716           default:
11717             break;
11718         }
11719     }
11720
11721     if (appData.debugMode)
11722       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11723
11724     if (cm == XBoardGame) {
11725         /* Skip any header junk before position diagram and/or move 1 */
11726         for (;;) {
11727             yyboardindex = forwardMostMove;
11728             cm = (ChessMove) Myylex();
11729
11730             if (cm == EndOfFile ||
11731                 cm == GNUChessGame || cm == XBoardGame) {
11732                 /* Empty game; pretend end-of-file and handle later */
11733                 cm = EndOfFile;
11734                 break;
11735             }
11736
11737             if (cm == MoveNumberOne || cm == PositionDiagram ||
11738                 cm == PGNTag || cm == Comment)
11739               break;
11740         }
11741     } else if (cm == GNUChessGame) {
11742         if (gameInfo.event != NULL) {
11743             free(gameInfo.event);
11744         }
11745         gameInfo.event = StrSave(yy_text);
11746     }
11747
11748     startedFromSetupPosition = FALSE;
11749     while (cm == PGNTag) {
11750         if (appData.debugMode)
11751           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11752         err = ParsePGNTag(yy_text, &gameInfo);
11753         if (!err) numPGNTags++;
11754
11755         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11756         if(gameInfo.variant != oldVariant) {
11757             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11758             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11759             InitPosition(TRUE);
11760             oldVariant = gameInfo.variant;
11761             if (appData.debugMode)
11762               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11763         }
11764
11765
11766         if (gameInfo.fen != NULL) {
11767           Board initial_position;
11768           startedFromSetupPosition = TRUE;
11769           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11770             Reset(TRUE, TRUE);
11771             DisplayError(_("Bad FEN position in file"), 0);
11772             return FALSE;
11773           }
11774           CopyBoard(boards[0], initial_position);
11775           if (blackPlaysFirst) {
11776             currentMove = forwardMostMove = backwardMostMove = 1;
11777             CopyBoard(boards[1], initial_position);
11778             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11779             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11780             timeRemaining[0][1] = whiteTimeRemaining;
11781             timeRemaining[1][1] = blackTimeRemaining;
11782             if (commentList[0] != NULL) {
11783               commentList[1] = commentList[0];
11784               commentList[0] = NULL;
11785             }
11786           } else {
11787             currentMove = forwardMostMove = backwardMostMove = 0;
11788           }
11789           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11790           {   int i;
11791               initialRulePlies = FENrulePlies;
11792               for( i=0; i< nrCastlingRights; i++ )
11793                   initialRights[i] = initial_position[CASTLING][i];
11794           }
11795           yyboardindex = forwardMostMove;
11796           free(gameInfo.fen);
11797           gameInfo.fen = NULL;
11798         }
11799
11800         yyboardindex = forwardMostMove;
11801         cm = (ChessMove) Myylex();
11802
11803         /* Handle comments interspersed among the tags */
11804         while (cm == Comment) {
11805             char *p;
11806             if (appData.debugMode)
11807               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11808             p = yy_text;
11809             AppendComment(currentMove, p, FALSE);
11810             yyboardindex = forwardMostMove;
11811             cm = (ChessMove) Myylex();
11812         }
11813     }
11814
11815     /* don't rely on existence of Event tag since if game was
11816      * pasted from clipboard the Event tag may not exist
11817      */
11818     if (numPGNTags > 0){
11819         char *tags;
11820         if (gameInfo.variant == VariantNormal) {
11821           VariantClass v = StringToVariant(gameInfo.event);
11822           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11823           if(v < VariantShogi) gameInfo.variant = v;
11824         }
11825         if (!matchMode) {
11826           if( appData.autoDisplayTags ) {
11827             tags = PGNTags(&gameInfo);
11828             TagsPopUp(tags, CmailMsg());
11829             free(tags);
11830           }
11831         }
11832     } else {
11833         /* Make something up, but don't display it now */
11834         SetGameInfo();
11835         TagsPopDown();
11836     }
11837
11838     if (cm == PositionDiagram) {
11839         int i, j;
11840         char *p;
11841         Board initial_position;
11842
11843         if (appData.debugMode)
11844           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11845
11846         if (!startedFromSetupPosition) {
11847             p = yy_text;
11848             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11849               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11850                 switch (*p) {
11851                   case '{':
11852                   case '[':
11853                   case '-':
11854                   case ' ':
11855                   case '\t':
11856                   case '\n':
11857                   case '\r':
11858                     break;
11859                   default:
11860                     initial_position[i][j++] = CharToPiece(*p);
11861                     break;
11862                 }
11863             while (*p == ' ' || *p == '\t' ||
11864                    *p == '\n' || *p == '\r') p++;
11865
11866             if (strncmp(p, "black", strlen("black"))==0)
11867               blackPlaysFirst = TRUE;
11868             else
11869               blackPlaysFirst = FALSE;
11870             startedFromSetupPosition = TRUE;
11871
11872             CopyBoard(boards[0], initial_position);
11873             if (blackPlaysFirst) {
11874                 currentMove = forwardMostMove = backwardMostMove = 1;
11875                 CopyBoard(boards[1], initial_position);
11876                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11877                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11878                 timeRemaining[0][1] = whiteTimeRemaining;
11879                 timeRemaining[1][1] = blackTimeRemaining;
11880                 if (commentList[0] != NULL) {
11881                     commentList[1] = commentList[0];
11882                     commentList[0] = NULL;
11883                 }
11884             } else {
11885                 currentMove = forwardMostMove = backwardMostMove = 0;
11886             }
11887         }
11888         yyboardindex = forwardMostMove;
11889         cm = (ChessMove) Myylex();
11890     }
11891
11892     if (first.pr == NoProc) {
11893         StartChessProgram(&first);
11894     }
11895     InitChessProgram(&first, FALSE);
11896     SendToProgram("force\n", &first);
11897     if (startedFromSetupPosition) {
11898         SendBoard(&first, forwardMostMove);
11899     if (appData.debugMode) {
11900         fprintf(debugFP, "Load Game\n");
11901     }
11902         DisplayBothClocks();
11903     }
11904
11905     /* [HGM] server: flag to write setup moves in broadcast file as one */
11906     loadFlag = appData.suppressLoadMoves;
11907
11908     while (cm == Comment) {
11909         char *p;
11910         if (appData.debugMode)
11911           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11912         p = yy_text;
11913         AppendComment(currentMove, p, FALSE);
11914         yyboardindex = forwardMostMove;
11915         cm = (ChessMove) Myylex();
11916     }
11917
11918     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11919         cm == WhiteWins || cm == BlackWins ||
11920         cm == GameIsDrawn || cm == GameUnfinished) {
11921         DisplayMessage("", _("No moves in game"));
11922         if (cmailMsgLoaded) {
11923             if (appData.debugMode)
11924               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11925             ClearHighlights();
11926             flipView = FALSE;
11927         }
11928         DrawPosition(FALSE, boards[currentMove]);
11929         DisplayBothClocks();
11930         gameMode = EditGame;
11931         ModeHighlight();
11932         gameFileFP = NULL;
11933         cmailOldMove = 0;
11934         return TRUE;
11935     }
11936
11937     // [HGM] PV info: routine tests if comment empty
11938     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11939         DisplayComment(currentMove - 1, commentList[currentMove]);
11940     }
11941     if (!matchMode && appData.timeDelay != 0)
11942       DrawPosition(FALSE, boards[currentMove]);
11943
11944     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11945       programStats.ok_to_send = 1;
11946     }
11947
11948     /* if the first token after the PGN tags is a move
11949      * and not move number 1, retrieve it from the parser
11950      */
11951     if (cm != MoveNumberOne)
11952         LoadGameOneMove(cm);
11953
11954     /* load the remaining moves from the file */
11955     while (LoadGameOneMove(EndOfFile)) {
11956       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11957       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11958     }
11959
11960     /* rewind to the start of the game */
11961     currentMove = backwardMostMove;
11962
11963     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11964
11965     if (oldGameMode == AnalyzeFile ||
11966         oldGameMode == AnalyzeMode) {
11967       AnalyzeFileEvent();
11968     }
11969
11970     if (!matchMode && pos >= 0) {
11971         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11972     } else
11973     if (matchMode || appData.timeDelay == 0) {
11974       ToEndEvent();
11975     } else if (appData.timeDelay > 0) {
11976       AutoPlayGameLoop();
11977     }
11978
11979     if (appData.debugMode)
11980         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11981
11982     loadFlag = 0; /* [HGM] true game starts */
11983     return TRUE;
11984 }
11985
11986 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11987 int
11988 ReloadPosition (int offset)
11989 {
11990     int positionNumber = lastLoadPositionNumber + offset;
11991     if (lastLoadPositionFP == NULL) {
11992         DisplayError(_("No position has been loaded yet"), 0);
11993         return FALSE;
11994     }
11995     if (positionNumber <= 0) {
11996         DisplayError(_("Can't back up any further"), 0);
11997         return FALSE;
11998     }
11999     return LoadPosition(lastLoadPositionFP, positionNumber,
12000                         lastLoadPositionTitle);
12001 }
12002
12003 /* Load the nth position from the given file */
12004 int
12005 LoadPositionFromFile (char *filename, int n, char *title)
12006 {
12007     FILE *f;
12008     char buf[MSG_SIZ];
12009
12010     if (strcmp(filename, "-") == 0) {
12011         return LoadPosition(stdin, n, "stdin");
12012     } else {
12013         f = fopen(filename, "rb");
12014         if (f == NULL) {
12015             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12016             DisplayError(buf, errno);
12017             return FALSE;
12018         } else {
12019             return LoadPosition(f, n, title);
12020         }
12021     }
12022 }
12023
12024 /* Load the nth position from the given open file, and close it */
12025 int
12026 LoadPosition (FILE *f, int positionNumber, char *title)
12027 {
12028     char *p, line[MSG_SIZ];
12029     Board initial_position;
12030     int i, j, fenMode, pn;
12031
12032     if (gameMode == Training )
12033         SetTrainingModeOff();
12034
12035     if (gameMode != BeginningOfGame) {
12036         Reset(FALSE, TRUE);
12037     }
12038     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12039         fclose(lastLoadPositionFP);
12040     }
12041     if (positionNumber == 0) positionNumber = 1;
12042     lastLoadPositionFP = f;
12043     lastLoadPositionNumber = positionNumber;
12044     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12045     if (first.pr == NoProc && !appData.noChessProgram) {
12046       StartChessProgram(&first);
12047       InitChessProgram(&first, FALSE);
12048     }
12049     pn = positionNumber;
12050     if (positionNumber < 0) {
12051         /* Negative position number means to seek to that byte offset */
12052         if (fseek(f, -positionNumber, 0) == -1) {
12053             DisplayError(_("Can't seek on position file"), 0);
12054             return FALSE;
12055         };
12056         pn = 1;
12057     } else {
12058         if (fseek(f, 0, 0) == -1) {
12059             if (f == lastLoadPositionFP ?
12060                 positionNumber == lastLoadPositionNumber + 1 :
12061                 positionNumber == 1) {
12062                 pn = 1;
12063             } else {
12064                 DisplayError(_("Can't seek on position file"), 0);
12065                 return FALSE;
12066             }
12067         }
12068     }
12069     /* See if this file is FEN or old-style xboard */
12070     if (fgets(line, MSG_SIZ, f) == NULL) {
12071         DisplayError(_("Position not found in file"), 0);
12072         return FALSE;
12073     }
12074     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12075     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12076
12077     if (pn >= 2) {
12078         if (fenMode || line[0] == '#') pn--;
12079         while (pn > 0) {
12080             /* skip positions before number pn */
12081             if (fgets(line, MSG_SIZ, f) == NULL) {
12082                 Reset(TRUE, TRUE);
12083                 DisplayError(_("Position not found in file"), 0);
12084                 return FALSE;
12085             }
12086             if (fenMode || line[0] == '#') pn--;
12087         }
12088     }
12089
12090     if (fenMode) {
12091         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12092             DisplayError(_("Bad FEN position in file"), 0);
12093             return FALSE;
12094         }
12095     } else {
12096         (void) fgets(line, MSG_SIZ, f);
12097         (void) fgets(line, MSG_SIZ, f);
12098
12099         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12100             (void) fgets(line, MSG_SIZ, f);
12101             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12102                 if (*p == ' ')
12103                   continue;
12104                 initial_position[i][j++] = CharToPiece(*p);
12105             }
12106         }
12107
12108         blackPlaysFirst = FALSE;
12109         if (!feof(f)) {
12110             (void) fgets(line, MSG_SIZ, f);
12111             if (strncmp(line, "black", strlen("black"))==0)
12112               blackPlaysFirst = TRUE;
12113         }
12114     }
12115     startedFromSetupPosition = TRUE;
12116
12117     CopyBoard(boards[0], initial_position);
12118     if (blackPlaysFirst) {
12119         currentMove = forwardMostMove = backwardMostMove = 1;
12120         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12121         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12122         CopyBoard(boards[1], initial_position);
12123         DisplayMessage("", _("Black to play"));
12124     } else {
12125         currentMove = forwardMostMove = backwardMostMove = 0;
12126         DisplayMessage("", _("White to play"));
12127     }
12128     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12129     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12130         SendToProgram("force\n", &first);
12131         SendBoard(&first, forwardMostMove);
12132     }
12133     if (appData.debugMode) {
12134 int i, j;
12135   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12136   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12137         fprintf(debugFP, "Load Position\n");
12138     }
12139
12140     if (positionNumber > 1) {
12141       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12142         DisplayTitle(line);
12143     } else {
12144         DisplayTitle(title);
12145     }
12146     gameMode = EditGame;
12147     ModeHighlight();
12148     ResetClocks();
12149     timeRemaining[0][1] = whiteTimeRemaining;
12150     timeRemaining[1][1] = blackTimeRemaining;
12151     DrawPosition(FALSE, boards[currentMove]);
12152
12153     return TRUE;
12154 }
12155
12156
12157 void
12158 CopyPlayerNameIntoFileName (char **dest, char *src)
12159 {
12160     while (*src != NULLCHAR && *src != ',') {
12161         if (*src == ' ') {
12162             *(*dest)++ = '_';
12163             src++;
12164         } else {
12165             *(*dest)++ = *src++;
12166         }
12167     }
12168 }
12169
12170 char *
12171 DefaultFileName (char *ext)
12172 {
12173     static char def[MSG_SIZ];
12174     char *p;
12175
12176     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12177         p = def;
12178         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12179         *p++ = '-';
12180         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12181         *p++ = '.';
12182         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12183     } else {
12184         def[0] = NULLCHAR;
12185     }
12186     return def;
12187 }
12188
12189 /* Save the current game to the given file */
12190 int
12191 SaveGameToFile (char *filename, int append)
12192 {
12193     FILE *f;
12194     char buf[MSG_SIZ];
12195     int result, i, t,tot=0;
12196
12197     if (strcmp(filename, "-") == 0) {
12198         return SaveGame(stdout, 0, NULL);
12199     } else {
12200         for(i=0; i<10; i++) { // upto 10 tries
12201              f = fopen(filename, append ? "a" : "w");
12202              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12203              if(f || errno != 13) break;
12204              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12205              tot += t;
12206         }
12207         if (f == NULL) {
12208             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12209             DisplayError(buf, errno);
12210             return FALSE;
12211         } else {
12212             safeStrCpy(buf, lastMsg, MSG_SIZ);
12213             DisplayMessage(_("Waiting for access to save file"), "");
12214             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12215             DisplayMessage(_("Saving game"), "");
12216             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12217             result = SaveGame(f, 0, NULL);
12218             DisplayMessage(buf, "");
12219             return result;
12220         }
12221     }
12222 }
12223
12224 char *
12225 SavePart (char *str)
12226 {
12227     static char buf[MSG_SIZ];
12228     char *p;
12229
12230     p = strchr(str, ' ');
12231     if (p == NULL) return str;
12232     strncpy(buf, str, p - str);
12233     buf[p - str] = NULLCHAR;
12234     return buf;
12235 }
12236
12237 #define PGN_MAX_LINE 75
12238
12239 #define PGN_SIDE_WHITE  0
12240 #define PGN_SIDE_BLACK  1
12241
12242 static int
12243 FindFirstMoveOutOfBook (int side)
12244 {
12245     int result = -1;
12246
12247     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12248         int index = backwardMostMove;
12249         int has_book_hit = 0;
12250
12251         if( (index % 2) != side ) {
12252             index++;
12253         }
12254
12255         while( index < forwardMostMove ) {
12256             /* Check to see if engine is in book */
12257             int depth = pvInfoList[index].depth;
12258             int score = pvInfoList[index].score;
12259             int in_book = 0;
12260
12261             if( depth <= 2 ) {
12262                 in_book = 1;
12263             }
12264             else if( score == 0 && depth == 63 ) {
12265                 in_book = 1; /* Zappa */
12266             }
12267             else if( score == 2 && depth == 99 ) {
12268                 in_book = 1; /* Abrok */
12269             }
12270
12271             has_book_hit += in_book;
12272
12273             if( ! in_book ) {
12274                 result = index;
12275
12276                 break;
12277             }
12278
12279             index += 2;
12280         }
12281     }
12282
12283     return result;
12284 }
12285
12286 void
12287 GetOutOfBookInfo (char * buf)
12288 {
12289     int oob[2];
12290     int i;
12291     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12292
12293     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12294     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12295
12296     *buf = '\0';
12297
12298     if( oob[0] >= 0 || oob[1] >= 0 ) {
12299         for( i=0; i<2; i++ ) {
12300             int idx = oob[i];
12301
12302             if( idx >= 0 ) {
12303                 if( i > 0 && oob[0] >= 0 ) {
12304                     strcat( buf, "   " );
12305                 }
12306
12307                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12308                 sprintf( buf+strlen(buf), "%s%.2f",
12309                     pvInfoList[idx].score >= 0 ? "+" : "",
12310                     pvInfoList[idx].score / 100.0 );
12311             }
12312         }
12313     }
12314 }
12315
12316 /* Save game in PGN style and close the file */
12317 int
12318 SaveGamePGN (FILE *f)
12319 {
12320     int i, offset, linelen, newblock;
12321     time_t tm;
12322 //    char *movetext;
12323     char numtext[32];
12324     int movelen, numlen, blank;
12325     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12326
12327     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12328
12329     tm = time((time_t *) NULL);
12330
12331     PrintPGNTags(f, &gameInfo);
12332
12333     if (backwardMostMove > 0 || startedFromSetupPosition) {
12334         char *fen = PositionToFEN(backwardMostMove, NULL);
12335         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12336         fprintf(f, "\n{--------------\n");
12337         PrintPosition(f, backwardMostMove);
12338         fprintf(f, "--------------}\n");
12339         free(fen);
12340     }
12341     else {
12342         /* [AS] Out of book annotation */
12343         if( appData.saveOutOfBookInfo ) {
12344             char buf[64];
12345
12346             GetOutOfBookInfo( buf );
12347
12348             if( buf[0] != '\0' ) {
12349                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12350             }
12351         }
12352
12353         fprintf(f, "\n");
12354     }
12355
12356     i = backwardMostMove;
12357     linelen = 0;
12358     newblock = TRUE;
12359
12360     while (i < forwardMostMove) {
12361         /* Print comments preceding this move */
12362         if (commentList[i] != NULL) {
12363             if (linelen > 0) fprintf(f, "\n");
12364             fprintf(f, "%s", commentList[i]);
12365             linelen = 0;
12366             newblock = TRUE;
12367         }
12368
12369         /* Format move number */
12370         if ((i % 2) == 0)
12371           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12372         else
12373           if (newblock)
12374             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12375           else
12376             numtext[0] = NULLCHAR;
12377
12378         numlen = strlen(numtext);
12379         newblock = FALSE;
12380
12381         /* Print move number */
12382         blank = linelen > 0 && numlen > 0;
12383         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12384             fprintf(f, "\n");
12385             linelen = 0;
12386             blank = 0;
12387         }
12388         if (blank) {
12389             fprintf(f, " ");
12390             linelen++;
12391         }
12392         fprintf(f, "%s", numtext);
12393         linelen += numlen;
12394
12395         /* Get move */
12396         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12397         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12398
12399         /* Print move */
12400         blank = linelen > 0 && movelen > 0;
12401         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12402             fprintf(f, "\n");
12403             linelen = 0;
12404             blank = 0;
12405         }
12406         if (blank) {
12407             fprintf(f, " ");
12408             linelen++;
12409         }
12410         fprintf(f, "%s", move_buffer);
12411         linelen += movelen;
12412
12413         /* [AS] Add PV info if present */
12414         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12415             /* [HGM] add time */
12416             char buf[MSG_SIZ]; int seconds;
12417
12418             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12419
12420             if( seconds <= 0)
12421               buf[0] = 0;
12422             else
12423               if( seconds < 30 )
12424                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12425               else
12426                 {
12427                   seconds = (seconds + 4)/10; // round to full seconds
12428                   if( seconds < 60 )
12429                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12430                   else
12431                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12432                 }
12433
12434             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12435                       pvInfoList[i].score >= 0 ? "+" : "",
12436                       pvInfoList[i].score / 100.0,
12437                       pvInfoList[i].depth,
12438                       buf );
12439
12440             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12441
12442             /* Print score/depth */
12443             blank = linelen > 0 && movelen > 0;
12444             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12445                 fprintf(f, "\n");
12446                 linelen = 0;
12447                 blank = 0;
12448             }
12449             if (blank) {
12450                 fprintf(f, " ");
12451                 linelen++;
12452             }
12453             fprintf(f, "%s", move_buffer);
12454             linelen += movelen;
12455         }
12456
12457         i++;
12458     }
12459
12460     /* Start a new line */
12461     if (linelen > 0) fprintf(f, "\n");
12462
12463     /* Print comments after last move */
12464     if (commentList[i] != NULL) {
12465         fprintf(f, "%s\n", commentList[i]);
12466     }
12467
12468     /* Print result */
12469     if (gameInfo.resultDetails != NULL &&
12470         gameInfo.resultDetails[0] != NULLCHAR) {
12471         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12472                 PGNResult(gameInfo.result));
12473     } else {
12474         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12475     }
12476
12477     fclose(f);
12478     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12479     return TRUE;
12480 }
12481
12482 /* Save game in old style and close the file */
12483 int
12484 SaveGameOldStyle (FILE *f)
12485 {
12486     int i, offset;
12487     time_t tm;
12488
12489     tm = time((time_t *) NULL);
12490
12491     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12492     PrintOpponents(f);
12493
12494     if (backwardMostMove > 0 || startedFromSetupPosition) {
12495         fprintf(f, "\n[--------------\n");
12496         PrintPosition(f, backwardMostMove);
12497         fprintf(f, "--------------]\n");
12498     } else {
12499         fprintf(f, "\n");
12500     }
12501
12502     i = backwardMostMove;
12503     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12504
12505     while (i < forwardMostMove) {
12506         if (commentList[i] != NULL) {
12507             fprintf(f, "[%s]\n", commentList[i]);
12508         }
12509
12510         if ((i % 2) == 1) {
12511             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12512             i++;
12513         } else {
12514             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12515             i++;
12516             if (commentList[i] != NULL) {
12517                 fprintf(f, "\n");
12518                 continue;
12519             }
12520             if (i >= forwardMostMove) {
12521                 fprintf(f, "\n");
12522                 break;
12523             }
12524             fprintf(f, "%s\n", parseList[i]);
12525             i++;
12526         }
12527     }
12528
12529     if (commentList[i] != NULL) {
12530         fprintf(f, "[%s]\n", commentList[i]);
12531     }
12532
12533     /* This isn't really the old style, but it's close enough */
12534     if (gameInfo.resultDetails != NULL &&
12535         gameInfo.resultDetails[0] != NULLCHAR) {
12536         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12537                 gameInfo.resultDetails);
12538     } else {
12539         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12540     }
12541
12542     fclose(f);
12543     return TRUE;
12544 }
12545
12546 /* Save the current game to open file f and close the file */
12547 int
12548 SaveGame (FILE *f, int dummy, char *dummy2)
12549 {
12550     if (gameMode == EditPosition) EditPositionDone(TRUE);
12551     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12552     if (appData.oldSaveStyle)
12553       return SaveGameOldStyle(f);
12554     else
12555       return SaveGamePGN(f);
12556 }
12557
12558 /* Save the current position to the given file */
12559 int
12560 SavePositionToFile (char *filename)
12561 {
12562     FILE *f;
12563     char buf[MSG_SIZ];
12564
12565     if (strcmp(filename, "-") == 0) {
12566         return SavePosition(stdout, 0, NULL);
12567     } else {
12568         f = fopen(filename, "a");
12569         if (f == NULL) {
12570             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12571             DisplayError(buf, errno);
12572             return FALSE;
12573         } else {
12574             safeStrCpy(buf, lastMsg, MSG_SIZ);
12575             DisplayMessage(_("Waiting for access to save file"), "");
12576             flock(fileno(f), LOCK_EX); // [HGM] lock
12577             DisplayMessage(_("Saving position"), "");
12578             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12579             SavePosition(f, 0, NULL);
12580             DisplayMessage(buf, "");
12581             return TRUE;
12582         }
12583     }
12584 }
12585
12586 /* Save the current position to the given open file and close the file */
12587 int
12588 SavePosition (FILE *f, int dummy, char *dummy2)
12589 {
12590     time_t tm;
12591     char *fen;
12592
12593     if (gameMode == EditPosition) EditPositionDone(TRUE);
12594     if (appData.oldSaveStyle) {
12595         tm = time((time_t *) NULL);
12596
12597         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12598         PrintOpponents(f);
12599         fprintf(f, "[--------------\n");
12600         PrintPosition(f, currentMove);
12601         fprintf(f, "--------------]\n");
12602     } else {
12603         fen = PositionToFEN(currentMove, NULL);
12604         fprintf(f, "%s\n", fen);
12605         free(fen);
12606     }
12607     fclose(f);
12608     return TRUE;
12609 }
12610
12611 void
12612 ReloadCmailMsgEvent (int unregister)
12613 {
12614 #if !WIN32
12615     static char *inFilename = NULL;
12616     static char *outFilename;
12617     int i;
12618     struct stat inbuf, outbuf;
12619     int status;
12620
12621     /* Any registered moves are unregistered if unregister is set, */
12622     /* i.e. invoked by the signal handler */
12623     if (unregister) {
12624         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12625             cmailMoveRegistered[i] = FALSE;
12626             if (cmailCommentList[i] != NULL) {
12627                 free(cmailCommentList[i]);
12628                 cmailCommentList[i] = NULL;
12629             }
12630         }
12631         nCmailMovesRegistered = 0;
12632     }
12633
12634     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12635         cmailResult[i] = CMAIL_NOT_RESULT;
12636     }
12637     nCmailResults = 0;
12638
12639     if (inFilename == NULL) {
12640         /* Because the filenames are static they only get malloced once  */
12641         /* and they never get freed                                      */
12642         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12643         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12644
12645         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12646         sprintf(outFilename, "%s.out", appData.cmailGameName);
12647     }
12648
12649     status = stat(outFilename, &outbuf);
12650     if (status < 0) {
12651         cmailMailedMove = FALSE;
12652     } else {
12653         status = stat(inFilename, &inbuf);
12654         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12655     }
12656
12657     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12658        counts the games, notes how each one terminated, etc.
12659
12660        It would be nice to remove this kludge and instead gather all
12661        the information while building the game list.  (And to keep it
12662        in the game list nodes instead of having a bunch of fixed-size
12663        parallel arrays.)  Note this will require getting each game's
12664        termination from the PGN tags, as the game list builder does
12665        not process the game moves.  --mann
12666        */
12667     cmailMsgLoaded = TRUE;
12668     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12669
12670     /* Load first game in the file or popup game menu */
12671     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12672
12673 #endif /* !WIN32 */
12674     return;
12675 }
12676
12677 int
12678 RegisterMove ()
12679 {
12680     FILE *f;
12681     char string[MSG_SIZ];
12682
12683     if (   cmailMailedMove
12684         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12685         return TRUE;            /* Allow free viewing  */
12686     }
12687
12688     /* Unregister move to ensure that we don't leave RegisterMove        */
12689     /* with the move registered when the conditions for registering no   */
12690     /* longer hold                                                       */
12691     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12692         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12693         nCmailMovesRegistered --;
12694
12695         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12696           {
12697               free(cmailCommentList[lastLoadGameNumber - 1]);
12698               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12699           }
12700     }
12701
12702     if (cmailOldMove == -1) {
12703         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12704         return FALSE;
12705     }
12706
12707     if (currentMove > cmailOldMove + 1) {
12708         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12709         return FALSE;
12710     }
12711
12712     if (currentMove < cmailOldMove) {
12713         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12714         return FALSE;
12715     }
12716
12717     if (forwardMostMove > currentMove) {
12718         /* Silently truncate extra moves */
12719         TruncateGame();
12720     }
12721
12722     if (   (currentMove == cmailOldMove + 1)
12723         || (   (currentMove == cmailOldMove)
12724             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12725                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12726         if (gameInfo.result != GameUnfinished) {
12727             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12728         }
12729
12730         if (commentList[currentMove] != NULL) {
12731             cmailCommentList[lastLoadGameNumber - 1]
12732               = StrSave(commentList[currentMove]);
12733         }
12734         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12735
12736         if (appData.debugMode)
12737           fprintf(debugFP, "Saving %s for game %d\n",
12738                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12739
12740         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12741
12742         f = fopen(string, "w");
12743         if (appData.oldSaveStyle) {
12744             SaveGameOldStyle(f); /* also closes the file */
12745
12746             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12747             f = fopen(string, "w");
12748             SavePosition(f, 0, NULL); /* also closes the file */
12749         } else {
12750             fprintf(f, "{--------------\n");
12751             PrintPosition(f, currentMove);
12752             fprintf(f, "--------------}\n\n");
12753
12754             SaveGame(f, 0, NULL); /* also closes the file*/
12755         }
12756
12757         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12758         nCmailMovesRegistered ++;
12759     } else if (nCmailGames == 1) {
12760         DisplayError(_("You have not made a move yet"), 0);
12761         return FALSE;
12762     }
12763
12764     return TRUE;
12765 }
12766
12767 void
12768 MailMoveEvent ()
12769 {
12770 #if !WIN32
12771     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12772     FILE *commandOutput;
12773     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12774     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12775     int nBuffers;
12776     int i;
12777     int archived;
12778     char *arcDir;
12779
12780     if (! cmailMsgLoaded) {
12781         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12782         return;
12783     }
12784
12785     if (nCmailGames == nCmailResults) {
12786         DisplayError(_("No unfinished games"), 0);
12787         return;
12788     }
12789
12790 #if CMAIL_PROHIBIT_REMAIL
12791     if (cmailMailedMove) {
12792       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);
12793         DisplayError(msg, 0);
12794         return;
12795     }
12796 #endif
12797
12798     if (! (cmailMailedMove || RegisterMove())) return;
12799
12800     if (   cmailMailedMove
12801         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12802       snprintf(string, MSG_SIZ, partCommandString,
12803                appData.debugMode ? " -v" : "", appData.cmailGameName);
12804         commandOutput = popen(string, "r");
12805
12806         if (commandOutput == NULL) {
12807             DisplayError(_("Failed to invoke cmail"), 0);
12808         } else {
12809             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12810                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12811             }
12812             if (nBuffers > 1) {
12813                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12814                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12815                 nBytes = MSG_SIZ - 1;
12816             } else {
12817                 (void) memcpy(msg, buffer, nBytes);
12818             }
12819             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12820
12821             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12822                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12823
12824                 archived = TRUE;
12825                 for (i = 0; i < nCmailGames; i ++) {
12826                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12827                         archived = FALSE;
12828                     }
12829                 }
12830                 if (   archived
12831                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12832                         != NULL)) {
12833                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12834                            arcDir,
12835                            appData.cmailGameName,
12836                            gameInfo.date);
12837                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12838                     cmailMsgLoaded = FALSE;
12839                 }
12840             }
12841
12842             DisplayInformation(msg);
12843             pclose(commandOutput);
12844         }
12845     } else {
12846         if ((*cmailMsg) != '\0') {
12847             DisplayInformation(cmailMsg);
12848         }
12849     }
12850
12851     return;
12852 #endif /* !WIN32 */
12853 }
12854
12855 char *
12856 CmailMsg ()
12857 {
12858 #if WIN32
12859     return NULL;
12860 #else
12861     int  prependComma = 0;
12862     char number[5];
12863     char string[MSG_SIZ];       /* Space for game-list */
12864     int  i;
12865
12866     if (!cmailMsgLoaded) return "";
12867
12868     if (cmailMailedMove) {
12869       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12870     } else {
12871         /* Create a list of games left */
12872       snprintf(string, MSG_SIZ, "[");
12873         for (i = 0; i < nCmailGames; i ++) {
12874             if (! (   cmailMoveRegistered[i]
12875                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12876                 if (prependComma) {
12877                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12878                 } else {
12879                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12880                     prependComma = 1;
12881                 }
12882
12883                 strcat(string, number);
12884             }
12885         }
12886         strcat(string, "]");
12887
12888         if (nCmailMovesRegistered + nCmailResults == 0) {
12889             switch (nCmailGames) {
12890               case 1:
12891                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12892                 break;
12893
12894               case 2:
12895                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12896                 break;
12897
12898               default:
12899                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12900                          nCmailGames);
12901                 break;
12902             }
12903         } else {
12904             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12905               case 1:
12906                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12907                          string);
12908                 break;
12909
12910               case 0:
12911                 if (nCmailResults == nCmailGames) {
12912                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12913                 } else {
12914                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12915                 }
12916                 break;
12917
12918               default:
12919                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12920                          string);
12921             }
12922         }
12923     }
12924     return cmailMsg;
12925 #endif /* WIN32 */
12926 }
12927
12928 void
12929 ResetGameEvent ()
12930 {
12931     if (gameMode == Training)
12932       SetTrainingModeOff();
12933
12934     Reset(TRUE, TRUE);
12935     cmailMsgLoaded = FALSE;
12936     if (appData.icsActive) {
12937       SendToICS(ics_prefix);
12938       SendToICS("refresh\n");
12939     }
12940 }
12941
12942 void
12943 ExitEvent (int status)
12944 {
12945     exiting++;
12946     if (exiting > 2) {
12947       /* Give up on clean exit */
12948       exit(status);
12949     }
12950     if (exiting > 1) {
12951       /* Keep trying for clean exit */
12952       return;
12953     }
12954
12955     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12956
12957     if (telnetISR != NULL) {
12958       RemoveInputSource(telnetISR);
12959     }
12960     if (icsPR != NoProc) {
12961       DestroyChildProcess(icsPR, TRUE);
12962     }
12963
12964     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12965     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12966
12967     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12968     /* make sure this other one finishes before killing it!                  */
12969     if(endingGame) { int count = 0;
12970         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12971         while(endingGame && count++ < 10) DoSleep(1);
12972         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12973     }
12974
12975     /* Kill off chess programs */
12976     if (first.pr != NoProc) {
12977         ExitAnalyzeMode();
12978
12979         DoSleep( appData.delayBeforeQuit );
12980         SendToProgram("quit\n", &first);
12981         DoSleep( appData.delayAfterQuit );
12982         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12983     }
12984     if (second.pr != NoProc) {
12985         DoSleep( appData.delayBeforeQuit );
12986         SendToProgram("quit\n", &second);
12987         DoSleep( appData.delayAfterQuit );
12988         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12989     }
12990     if (first.isr != NULL) {
12991         RemoveInputSource(first.isr);
12992     }
12993     if (second.isr != NULL) {
12994         RemoveInputSource(second.isr);
12995     }
12996
12997     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12998     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12999
13000     ShutDownFrontEnd();
13001     exit(status);
13002 }
13003
13004 void
13005 PauseEvent ()
13006 {
13007     if (appData.debugMode)
13008         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13009     if (pausing) {
13010         pausing = FALSE;
13011         ModeHighlight();
13012         if (gameMode == MachinePlaysWhite ||
13013             gameMode == MachinePlaysBlack) {
13014             StartClocks();
13015         } else {
13016             DisplayBothClocks();
13017         }
13018         if (gameMode == PlayFromGameFile) {
13019             if (appData.timeDelay >= 0)
13020                 AutoPlayGameLoop();
13021         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13022             Reset(FALSE, TRUE);
13023             SendToICS(ics_prefix);
13024             SendToICS("refresh\n");
13025         } else if (currentMove < forwardMostMove) {
13026             ForwardInner(forwardMostMove);
13027         }
13028         pauseExamInvalid = FALSE;
13029     } else {
13030         switch (gameMode) {
13031           default:
13032             return;
13033           case IcsExamining:
13034             pauseExamForwardMostMove = forwardMostMove;
13035             pauseExamInvalid = FALSE;
13036             /* fall through */
13037           case IcsObserving:
13038           case IcsPlayingWhite:
13039           case IcsPlayingBlack:
13040             pausing = TRUE;
13041             ModeHighlight();
13042             return;
13043           case PlayFromGameFile:
13044             (void) StopLoadGameTimer();
13045             pausing = TRUE;
13046             ModeHighlight();
13047             break;
13048           case BeginningOfGame:
13049             if (appData.icsActive) return;
13050             /* else fall through */
13051           case MachinePlaysWhite:
13052           case MachinePlaysBlack:
13053           case TwoMachinesPlay:
13054             if (forwardMostMove == 0)
13055               return;           /* don't pause if no one has moved */
13056             if ((gameMode == MachinePlaysWhite &&
13057                  !WhiteOnMove(forwardMostMove)) ||
13058                 (gameMode == MachinePlaysBlack &&
13059                  WhiteOnMove(forwardMostMove))) {
13060                 StopClocks();
13061             }
13062             pausing = TRUE;
13063             ModeHighlight();
13064             break;
13065         }
13066     }
13067 }
13068
13069 void
13070 EditCommentEvent ()
13071 {
13072     char title[MSG_SIZ];
13073
13074     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13075       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13076     } else {
13077       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13078                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13079                parseList[currentMove - 1]);
13080     }
13081
13082     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13083 }
13084
13085
13086 void
13087 EditTagsEvent ()
13088 {
13089     char *tags = PGNTags(&gameInfo);
13090     bookUp = FALSE;
13091     EditTagsPopUp(tags, NULL);
13092     free(tags);
13093 }
13094
13095 void
13096 AnalyzeModeEvent ()
13097 {
13098     if (appData.noChessProgram || gameMode == AnalyzeMode)
13099       return;
13100
13101     if (gameMode != AnalyzeFile) {
13102         if (!appData.icsEngineAnalyze) {
13103                EditGameEvent();
13104                if (gameMode != EditGame) return;
13105         }
13106         ResurrectChessProgram();
13107         SendToProgram("analyze\n", &first);
13108         first.analyzing = TRUE;
13109         /*first.maybeThinking = TRUE;*/
13110         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13111         EngineOutputPopUp();
13112     }
13113     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13114     pausing = FALSE;
13115     ModeHighlight();
13116     SetGameInfo();
13117
13118     StartAnalysisClock();
13119     GetTimeMark(&lastNodeCountTime);
13120     lastNodeCount = 0;
13121 }
13122
13123 void
13124 AnalyzeFileEvent ()
13125 {
13126     if (appData.noChessProgram || gameMode == AnalyzeFile)
13127       return;
13128
13129     if (gameMode != AnalyzeMode) {
13130         EditGameEvent();
13131         if (gameMode != EditGame) return;
13132         ResurrectChessProgram();
13133         SendToProgram("analyze\n", &first);
13134         first.analyzing = TRUE;
13135         /*first.maybeThinking = TRUE;*/
13136         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13137         EngineOutputPopUp();
13138     }
13139     gameMode = AnalyzeFile;
13140     pausing = FALSE;
13141     ModeHighlight();
13142     SetGameInfo();
13143
13144     StartAnalysisClock();
13145     GetTimeMark(&lastNodeCountTime);
13146     lastNodeCount = 0;
13147     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13148 }
13149
13150 void
13151 MachineWhiteEvent ()
13152 {
13153     char buf[MSG_SIZ];
13154     char *bookHit = NULL;
13155
13156     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13157       return;
13158
13159
13160     if (gameMode == PlayFromGameFile ||
13161         gameMode == TwoMachinesPlay  ||
13162         gameMode == Training         ||
13163         gameMode == AnalyzeMode      ||
13164         gameMode == EndOfGame)
13165         EditGameEvent();
13166
13167     if (gameMode == EditPosition)
13168         EditPositionDone(TRUE);
13169
13170     if (!WhiteOnMove(currentMove)) {
13171         DisplayError(_("It is not White's turn"), 0);
13172         return;
13173     }
13174
13175     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13176       ExitAnalyzeMode();
13177
13178     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13179         gameMode == AnalyzeFile)
13180         TruncateGame();
13181
13182     ResurrectChessProgram();    /* in case it isn't running */
13183     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13184         gameMode = MachinePlaysWhite;
13185         ResetClocks();
13186     } else
13187     gameMode = MachinePlaysWhite;
13188     pausing = FALSE;
13189     ModeHighlight();
13190     SetGameInfo();
13191     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13192     DisplayTitle(buf);
13193     if (first.sendName) {
13194       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13195       SendToProgram(buf, &first);
13196     }
13197     if (first.sendTime) {
13198       if (first.useColors) {
13199         SendToProgram("black\n", &first); /*gnu kludge*/
13200       }
13201       SendTimeRemaining(&first, TRUE);
13202     }
13203     if (first.useColors) {
13204       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13205     }
13206     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13207     SetMachineThinkingEnables();
13208     first.maybeThinking = TRUE;
13209     StartClocks();
13210     firstMove = FALSE;
13211
13212     if (appData.autoFlipView && !flipView) {
13213       flipView = !flipView;
13214       DrawPosition(FALSE, NULL);
13215       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13216     }
13217
13218     if(bookHit) { // [HGM] book: simulate book reply
13219         static char bookMove[MSG_SIZ]; // a bit generous?
13220
13221         programStats.nodes = programStats.depth = programStats.time =
13222         programStats.score = programStats.got_only_move = 0;
13223         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13224
13225         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13226         strcat(bookMove, bookHit);
13227         HandleMachineMove(bookMove, &first);
13228     }
13229 }
13230
13231 void
13232 MachineBlackEvent ()
13233 {
13234   char buf[MSG_SIZ];
13235   char *bookHit = NULL;
13236
13237     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13238         return;
13239
13240
13241     if (gameMode == PlayFromGameFile ||
13242         gameMode == TwoMachinesPlay  ||
13243         gameMode == Training         ||
13244         gameMode == AnalyzeMode      ||
13245         gameMode == EndOfGame)
13246         EditGameEvent();
13247
13248     if (gameMode == EditPosition)
13249         EditPositionDone(TRUE);
13250
13251     if (WhiteOnMove(currentMove)) {
13252         DisplayError(_("It is not Black's turn"), 0);
13253         return;
13254     }
13255
13256     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13257       ExitAnalyzeMode();
13258
13259     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13260         gameMode == AnalyzeFile)
13261         TruncateGame();
13262
13263     ResurrectChessProgram();    /* in case it isn't running */
13264     gameMode = MachinePlaysBlack;
13265     pausing = FALSE;
13266     ModeHighlight();
13267     SetGameInfo();
13268     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13269     DisplayTitle(buf);
13270     if (first.sendName) {
13271       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13272       SendToProgram(buf, &first);
13273     }
13274     if (first.sendTime) {
13275       if (first.useColors) {
13276         SendToProgram("white\n", &first); /*gnu kludge*/
13277       }
13278       SendTimeRemaining(&first, FALSE);
13279     }
13280     if (first.useColors) {
13281       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13282     }
13283     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13284     SetMachineThinkingEnables();
13285     first.maybeThinking = TRUE;
13286     StartClocks();
13287
13288     if (appData.autoFlipView && flipView) {
13289       flipView = !flipView;
13290       DrawPosition(FALSE, NULL);
13291       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13292     }
13293     if(bookHit) { // [HGM] book: simulate book reply
13294         static char bookMove[MSG_SIZ]; // a bit generous?
13295
13296         programStats.nodes = programStats.depth = programStats.time =
13297         programStats.score = programStats.got_only_move = 0;
13298         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13299
13300         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13301         strcat(bookMove, bookHit);
13302         HandleMachineMove(bookMove, &first);
13303     }
13304 }
13305
13306
13307 void
13308 DisplayTwoMachinesTitle ()
13309 {
13310     char buf[MSG_SIZ];
13311     if (appData.matchGames > 0) {
13312         if(appData.tourneyFile[0]) {
13313           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13314                    gameInfo.white, _("vs."), gameInfo.black,
13315                    nextGame+1, appData.matchGames+1,
13316                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13317         } else 
13318         if (first.twoMachinesColor[0] == 'w') {
13319           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13320                    gameInfo.white, _("vs."),  gameInfo.black,
13321                    first.matchWins, second.matchWins,
13322                    matchGame - 1 - (first.matchWins + second.matchWins));
13323         } else {
13324           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13325                    gameInfo.white, _("vs."), gameInfo.black,
13326                    second.matchWins, first.matchWins,
13327                    matchGame - 1 - (first.matchWins + second.matchWins));
13328         }
13329     } else {
13330       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13331     }
13332     DisplayTitle(buf);
13333 }
13334
13335 void
13336 SettingsMenuIfReady ()
13337 {
13338   if (second.lastPing != second.lastPong) {
13339     DisplayMessage("", _("Waiting for second chess program"));
13340     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13341     return;
13342   }
13343   ThawUI();
13344   DisplayMessage("", "");
13345   SettingsPopUp(&second);
13346 }
13347
13348 int
13349 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13350 {
13351     char buf[MSG_SIZ];
13352     if (cps->pr == NoProc) {
13353         StartChessProgram(cps);
13354         if (cps->protocolVersion == 1) {
13355           retry();
13356         } else {
13357           /* kludge: allow timeout for initial "feature" command */
13358           FreezeUI();
13359           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13360           DisplayMessage("", buf);
13361           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13362         }
13363         return 1;
13364     }
13365     return 0;
13366 }
13367
13368 void
13369 TwoMachinesEvent P((void))
13370 {
13371     int i;
13372     char buf[MSG_SIZ];
13373     ChessProgramState *onmove;
13374     char *bookHit = NULL;
13375     static int stalling = 0;
13376     TimeMark now;
13377     long wait;
13378
13379     if (appData.noChessProgram) return;
13380
13381     switch (gameMode) {
13382       case TwoMachinesPlay:
13383         return;
13384       case MachinePlaysWhite:
13385       case MachinePlaysBlack:
13386         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13387             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13388             return;
13389         }
13390         /* fall through */
13391       case BeginningOfGame:
13392       case PlayFromGameFile:
13393       case EndOfGame:
13394         EditGameEvent();
13395         if (gameMode != EditGame) return;
13396         break;
13397       case EditPosition:
13398         EditPositionDone(TRUE);
13399         break;
13400       case AnalyzeMode:
13401       case AnalyzeFile:
13402         ExitAnalyzeMode();
13403         break;
13404       case EditGame:
13405       default:
13406         break;
13407     }
13408
13409     if(matchMode && appData.debugMode) { // [HGM] debug split: game is part of a match; we might have to create a debug file just for this game
13410         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
13411         if(strcmp(buf, currentDebugFile)) { // name has changed
13412             FILE *f = fopen(buf, "w");
13413             if(f) { // if opening the new file failed, just keep using the old one
13414                 ASSIGN(currentDebugFile, buf);
13415                 fclose(debugFP);
13416                 debugFP = f;
13417             }
13418         }
13419     }
13420 //    forwardMostMove = currentMove;
13421     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13422
13423     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13424
13425     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13426     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13427       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13428       return;
13429     }
13430     if(!stalling) {
13431       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13432       SendToProgram("force\n", &second);
13433       stalling = 1;
13434       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13435       return;
13436     }
13437     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13438     if(appData.matchPause>10000 || appData.matchPause<10)
13439                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13440     wait = SubtractTimeMarks(&now, &pauseStart);
13441     if(wait < appData.matchPause) {
13442         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13443         return;
13444     }
13445     // we are now committed to starting the game
13446     stalling = 0;
13447     DisplayMessage("", "");
13448     if (startedFromSetupPosition) {
13449         SendBoard(&second, backwardMostMove);
13450     if (appData.debugMode) {
13451         fprintf(debugFP, "Two Machines\n");
13452     }
13453     }
13454     for (i = backwardMostMove; i < forwardMostMove; i++) {
13455         SendMoveToProgram(i, &second);
13456     }
13457
13458     gameMode = TwoMachinesPlay;
13459     pausing = FALSE;
13460     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13461     SetGameInfo();
13462     DisplayTwoMachinesTitle();
13463     firstMove = TRUE;
13464     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13465         onmove = &first;
13466     } else {
13467         onmove = &second;
13468     }
13469     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13470     SendToProgram(first.computerString, &first);
13471     if (first.sendName) {
13472       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13473       SendToProgram(buf, &first);
13474     }
13475     SendToProgram(second.computerString, &second);
13476     if (second.sendName) {
13477       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13478       SendToProgram(buf, &second);
13479     }
13480
13481     ResetClocks();
13482     if (!first.sendTime || !second.sendTime) {
13483         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13484         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13485     }
13486     if (onmove->sendTime) {
13487       if (onmove->useColors) {
13488         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13489       }
13490       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13491     }
13492     if (onmove->useColors) {
13493       SendToProgram(onmove->twoMachinesColor, onmove);
13494     }
13495     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13496 //    SendToProgram("go\n", onmove);
13497     onmove->maybeThinking = TRUE;
13498     SetMachineThinkingEnables();
13499
13500     StartClocks();
13501
13502     if(bookHit) { // [HGM] book: simulate book reply
13503         static char bookMove[MSG_SIZ]; // a bit generous?
13504
13505         programStats.nodes = programStats.depth = programStats.time =
13506         programStats.score = programStats.got_only_move = 0;
13507         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13508
13509         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13510         strcat(bookMove, bookHit);
13511         savedMessage = bookMove; // args for deferred call
13512         savedState = onmove;
13513         ScheduleDelayedEvent(DeferredBookMove, 1);
13514     }
13515 }
13516
13517 void
13518 TrainingEvent ()
13519 {
13520     if (gameMode == Training) {
13521       SetTrainingModeOff();
13522       gameMode = PlayFromGameFile;
13523       DisplayMessage("", _("Training mode off"));
13524     } else {
13525       gameMode = Training;
13526       animateTraining = appData.animate;
13527
13528       /* make sure we are not already at the end of the game */
13529       if (currentMove < forwardMostMove) {
13530         SetTrainingModeOn();
13531         DisplayMessage("", _("Training mode on"));
13532       } else {
13533         gameMode = PlayFromGameFile;
13534         DisplayError(_("Already at end of game"), 0);
13535       }
13536     }
13537     ModeHighlight();
13538 }
13539
13540 void
13541 IcsClientEvent ()
13542 {
13543     if (!appData.icsActive) return;
13544     switch (gameMode) {
13545       case IcsPlayingWhite:
13546       case IcsPlayingBlack:
13547       case IcsObserving:
13548       case IcsIdle:
13549       case BeginningOfGame:
13550       case IcsExamining:
13551         return;
13552
13553       case EditGame:
13554         break;
13555
13556       case EditPosition:
13557         EditPositionDone(TRUE);
13558         break;
13559
13560       case AnalyzeMode:
13561       case AnalyzeFile:
13562         ExitAnalyzeMode();
13563         break;
13564
13565       default:
13566         EditGameEvent();
13567         break;
13568     }
13569
13570     gameMode = IcsIdle;
13571     ModeHighlight();
13572     return;
13573 }
13574
13575 void
13576 EditGameEvent ()
13577 {
13578     int i;
13579
13580     switch (gameMode) {
13581       case Training:
13582         SetTrainingModeOff();
13583         break;
13584       case MachinePlaysWhite:
13585       case MachinePlaysBlack:
13586       case BeginningOfGame:
13587         SendToProgram("force\n", &first);
13588         SetUserThinkingEnables();
13589         break;
13590       case PlayFromGameFile:
13591         (void) StopLoadGameTimer();
13592         if (gameFileFP != NULL) {
13593             gameFileFP = NULL;
13594         }
13595         break;
13596       case EditPosition:
13597         EditPositionDone(TRUE);
13598         break;
13599       case AnalyzeMode:
13600       case AnalyzeFile:
13601         ExitAnalyzeMode();
13602         SendToProgram("force\n", &first);
13603         break;
13604       case TwoMachinesPlay:
13605         GameEnds(EndOfFile, NULL, GE_PLAYER);
13606         ResurrectChessProgram();
13607         SetUserThinkingEnables();
13608         break;
13609       case EndOfGame:
13610         ResurrectChessProgram();
13611         break;
13612       case IcsPlayingBlack:
13613       case IcsPlayingWhite:
13614         DisplayError(_("Warning: You are still playing a game"), 0);
13615         break;
13616       case IcsObserving:
13617         DisplayError(_("Warning: You are still observing a game"), 0);
13618         break;
13619       case IcsExamining:
13620         DisplayError(_("Warning: You are still examining a game"), 0);
13621         break;
13622       case IcsIdle:
13623         break;
13624       case EditGame:
13625       default:
13626         return;
13627     }
13628
13629     pausing = FALSE;
13630     StopClocks();
13631     first.offeredDraw = second.offeredDraw = 0;
13632
13633     if (gameMode == PlayFromGameFile) {
13634         whiteTimeRemaining = timeRemaining[0][currentMove];
13635         blackTimeRemaining = timeRemaining[1][currentMove];
13636         DisplayTitle("");
13637     }
13638
13639     if (gameMode == MachinePlaysWhite ||
13640         gameMode == MachinePlaysBlack ||
13641         gameMode == TwoMachinesPlay ||
13642         gameMode == EndOfGame) {
13643         i = forwardMostMove;
13644         while (i > currentMove) {
13645             SendToProgram("undo\n", &first);
13646             i--;
13647         }
13648         if(!adjustedClock) {
13649         whiteTimeRemaining = timeRemaining[0][currentMove];
13650         blackTimeRemaining = timeRemaining[1][currentMove];
13651         DisplayBothClocks();
13652         }
13653         if (whiteFlag || blackFlag) {
13654             whiteFlag = blackFlag = 0;
13655         }
13656         DisplayTitle("");
13657     }
13658
13659     gameMode = EditGame;
13660     ModeHighlight();
13661     SetGameInfo();
13662 }
13663
13664
13665 void
13666 EditPositionEvent ()
13667 {
13668     if (gameMode == EditPosition) {
13669         EditGameEvent();
13670         return;
13671     }
13672
13673     EditGameEvent();
13674     if (gameMode != EditGame) return;
13675
13676     gameMode = EditPosition;
13677     ModeHighlight();
13678     SetGameInfo();
13679     if (currentMove > 0)
13680       CopyBoard(boards[0], boards[currentMove]);
13681
13682     blackPlaysFirst = !WhiteOnMove(currentMove);
13683     ResetClocks();
13684     currentMove = forwardMostMove = backwardMostMove = 0;
13685     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13686     DisplayMove(-1);
13687 }
13688
13689 void
13690 ExitAnalyzeMode ()
13691 {
13692     /* [DM] icsEngineAnalyze - possible call from other functions */
13693     if (appData.icsEngineAnalyze) {
13694         appData.icsEngineAnalyze = FALSE;
13695
13696         DisplayMessage("",_("Close ICS engine analyze..."));
13697     }
13698     if (first.analysisSupport && first.analyzing) {
13699       SendToProgram("exit\n", &first);
13700       first.analyzing = FALSE;
13701     }
13702     thinkOutput[0] = NULLCHAR;
13703 }
13704
13705 void
13706 EditPositionDone (Boolean fakeRights)
13707 {
13708     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13709
13710     startedFromSetupPosition = TRUE;
13711     InitChessProgram(&first, FALSE);
13712     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13713       boards[0][EP_STATUS] = EP_NONE;
13714       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13715     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13716         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13717         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13718       } else boards[0][CASTLING][2] = NoRights;
13719     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13720         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13721         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13722       } else boards[0][CASTLING][5] = NoRights;
13723     }
13724     SendToProgram("force\n", &first);
13725     if (blackPlaysFirst) {
13726         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13727         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13728         currentMove = forwardMostMove = backwardMostMove = 1;
13729         CopyBoard(boards[1], boards[0]);
13730     } else {
13731         currentMove = forwardMostMove = backwardMostMove = 0;
13732     }
13733     SendBoard(&first, forwardMostMove);
13734     if (appData.debugMode) {
13735         fprintf(debugFP, "EditPosDone\n");
13736     }
13737     DisplayTitle("");
13738     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13739     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13740     gameMode = EditGame;
13741     ModeHighlight();
13742     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13743     ClearHighlights(); /* [AS] */
13744 }
13745
13746 /* Pause for `ms' milliseconds */
13747 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13748 void
13749 TimeDelay (long ms)
13750 {
13751     TimeMark m1, m2;
13752
13753     GetTimeMark(&m1);
13754     do {
13755         GetTimeMark(&m2);
13756     } while (SubtractTimeMarks(&m2, &m1) < ms);
13757 }
13758
13759 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13760 void
13761 SendMultiLineToICS (char *buf)
13762 {
13763     char temp[MSG_SIZ+1], *p;
13764     int len;
13765
13766     len = strlen(buf);
13767     if (len > MSG_SIZ)
13768       len = MSG_SIZ;
13769
13770     strncpy(temp, buf, len);
13771     temp[len] = 0;
13772
13773     p = temp;
13774     while (*p) {
13775         if (*p == '\n' || *p == '\r')
13776           *p = ' ';
13777         ++p;
13778     }
13779
13780     strcat(temp, "\n");
13781     SendToICS(temp);
13782     SendToPlayer(temp, strlen(temp));
13783 }
13784
13785 void
13786 SetWhiteToPlayEvent ()
13787 {
13788     if (gameMode == EditPosition) {
13789         blackPlaysFirst = FALSE;
13790         DisplayBothClocks();    /* works because currentMove is 0 */
13791     } else if (gameMode == IcsExamining) {
13792         SendToICS(ics_prefix);
13793         SendToICS("tomove white\n");
13794     }
13795 }
13796
13797 void
13798 SetBlackToPlayEvent ()
13799 {
13800     if (gameMode == EditPosition) {
13801         blackPlaysFirst = TRUE;
13802         currentMove = 1;        /* kludge */
13803         DisplayBothClocks();
13804         currentMove = 0;
13805     } else if (gameMode == IcsExamining) {
13806         SendToICS(ics_prefix);
13807         SendToICS("tomove black\n");
13808     }
13809 }
13810
13811 void
13812 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13813 {
13814     char buf[MSG_SIZ];
13815     ChessSquare piece = boards[0][y][x];
13816
13817     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13818
13819     switch (selection) {
13820       case ClearBoard:
13821         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13822             SendToICS(ics_prefix);
13823             SendToICS("bsetup clear\n");
13824         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13825             SendToICS(ics_prefix);
13826             SendToICS("clearboard\n");
13827         } else {
13828             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13829                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13830                 for (y = 0; y < BOARD_HEIGHT; y++) {
13831                     if (gameMode == IcsExamining) {
13832                         if (boards[currentMove][y][x] != EmptySquare) {
13833                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13834                                     AAA + x, ONE + y);
13835                             SendToICS(buf);
13836                         }
13837                     } else {
13838                         boards[0][y][x] = p;
13839                     }
13840                 }
13841             }
13842         }
13843         if (gameMode == EditPosition) {
13844             DrawPosition(FALSE, boards[0]);
13845         }
13846         break;
13847
13848       case WhitePlay:
13849         SetWhiteToPlayEvent();
13850         break;
13851
13852       case BlackPlay:
13853         SetBlackToPlayEvent();
13854         break;
13855
13856       case EmptySquare:
13857         if (gameMode == IcsExamining) {
13858             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13859             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13860             SendToICS(buf);
13861         } else {
13862             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13863                 if(x == BOARD_LEFT-2) {
13864                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13865                     boards[0][y][1] = 0;
13866                 } else
13867                 if(x == BOARD_RGHT+1) {
13868                     if(y >= gameInfo.holdingsSize) break;
13869                     boards[0][y][BOARD_WIDTH-2] = 0;
13870                 } else break;
13871             }
13872             boards[0][y][x] = EmptySquare;
13873             DrawPosition(FALSE, boards[0]);
13874         }
13875         break;
13876
13877       case PromotePiece:
13878         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13879            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13880             selection = (ChessSquare) (PROMOTED piece);
13881         } else if(piece == EmptySquare) selection = WhiteSilver;
13882         else selection = (ChessSquare)((int)piece - 1);
13883         goto defaultlabel;
13884
13885       case DemotePiece:
13886         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13887            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13888             selection = (ChessSquare) (DEMOTED piece);
13889         } else if(piece == EmptySquare) selection = BlackSilver;
13890         else selection = (ChessSquare)((int)piece + 1);
13891         goto defaultlabel;
13892
13893       case WhiteQueen:
13894       case BlackQueen:
13895         if(gameInfo.variant == VariantShatranj ||
13896            gameInfo.variant == VariantXiangqi  ||
13897            gameInfo.variant == VariantCourier  ||
13898            gameInfo.variant == VariantMakruk     )
13899             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13900         goto defaultlabel;
13901
13902       case WhiteKing:
13903       case BlackKing:
13904         if(gameInfo.variant == VariantXiangqi)
13905             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13906         if(gameInfo.variant == VariantKnightmate)
13907             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13908       default:
13909         defaultlabel:
13910         if (gameMode == IcsExamining) {
13911             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13912             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13913                      PieceToChar(selection), AAA + x, ONE + y);
13914             SendToICS(buf);
13915         } else {
13916             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13917                 int n;
13918                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13919                     n = PieceToNumber(selection - BlackPawn);
13920                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13921                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13922                     boards[0][BOARD_HEIGHT-1-n][1]++;
13923                 } else
13924                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13925                     n = PieceToNumber(selection);
13926                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13927                     boards[0][n][BOARD_WIDTH-1] = selection;
13928                     boards[0][n][BOARD_WIDTH-2]++;
13929                 }
13930             } else
13931             boards[0][y][x] = selection;
13932             DrawPosition(TRUE, boards[0]);
13933         }
13934         break;
13935     }
13936 }
13937
13938
13939 void
13940 DropMenuEvent (ChessSquare selection, int x, int y)
13941 {
13942     ChessMove moveType;
13943
13944     switch (gameMode) {
13945       case IcsPlayingWhite:
13946       case MachinePlaysBlack:
13947         if (!WhiteOnMove(currentMove)) {
13948             DisplayMoveError(_("It is Black's turn"));
13949             return;
13950         }
13951         moveType = WhiteDrop;
13952         break;
13953       case IcsPlayingBlack:
13954       case MachinePlaysWhite:
13955         if (WhiteOnMove(currentMove)) {
13956             DisplayMoveError(_("It is White's turn"));
13957             return;
13958         }
13959         moveType = BlackDrop;
13960         break;
13961       case EditGame:
13962         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13963         break;
13964       default:
13965         return;
13966     }
13967
13968     if (moveType == BlackDrop && selection < BlackPawn) {
13969       selection = (ChessSquare) ((int) selection
13970                                  + (int) BlackPawn - (int) WhitePawn);
13971     }
13972     if (boards[currentMove][y][x] != EmptySquare) {
13973         DisplayMoveError(_("That square is occupied"));
13974         return;
13975     }
13976
13977     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13978 }
13979
13980 void
13981 AcceptEvent ()
13982 {
13983     /* Accept a pending offer of any kind from opponent */
13984
13985     if (appData.icsActive) {
13986         SendToICS(ics_prefix);
13987         SendToICS("accept\n");
13988     } else if (cmailMsgLoaded) {
13989         if (currentMove == cmailOldMove &&
13990             commentList[cmailOldMove] != NULL &&
13991             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13992                    "Black offers a draw" : "White offers a draw")) {
13993             TruncateGame();
13994             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13995             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13996         } else {
13997             DisplayError(_("There is no pending offer on this move"), 0);
13998             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13999         }
14000     } else {
14001         /* Not used for offers from chess program */
14002     }
14003 }
14004
14005 void
14006 DeclineEvent ()
14007 {
14008     /* Decline a pending offer of any kind from opponent */
14009
14010     if (appData.icsActive) {
14011         SendToICS(ics_prefix);
14012         SendToICS("decline\n");
14013     } else if (cmailMsgLoaded) {
14014         if (currentMove == cmailOldMove &&
14015             commentList[cmailOldMove] != NULL &&
14016             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14017                    "Black offers a draw" : "White offers a draw")) {
14018 #ifdef NOTDEF
14019             AppendComment(cmailOldMove, "Draw declined", TRUE);
14020             DisplayComment(cmailOldMove - 1, "Draw declined");
14021 #endif /*NOTDEF*/
14022         } else {
14023             DisplayError(_("There is no pending offer on this move"), 0);
14024         }
14025     } else {
14026         /* Not used for offers from chess program */
14027     }
14028 }
14029
14030 void
14031 RematchEvent ()
14032 {
14033     /* Issue ICS rematch command */
14034     if (appData.icsActive) {
14035         SendToICS(ics_prefix);
14036         SendToICS("rematch\n");
14037     }
14038 }
14039
14040 void
14041 CallFlagEvent ()
14042 {
14043     /* Call your opponent's flag (claim a win on time) */
14044     if (appData.icsActive) {
14045         SendToICS(ics_prefix);
14046         SendToICS("flag\n");
14047     } else {
14048         switch (gameMode) {
14049           default:
14050             return;
14051           case MachinePlaysWhite:
14052             if (whiteFlag) {
14053                 if (blackFlag)
14054                   GameEnds(GameIsDrawn, "Both players ran out of time",
14055                            GE_PLAYER);
14056                 else
14057                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14058             } else {
14059                 DisplayError(_("Your opponent is not out of time"), 0);
14060             }
14061             break;
14062           case MachinePlaysBlack:
14063             if (blackFlag) {
14064                 if (whiteFlag)
14065                   GameEnds(GameIsDrawn, "Both players ran out of time",
14066                            GE_PLAYER);
14067                 else
14068                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14069             } else {
14070                 DisplayError(_("Your opponent is not out of time"), 0);
14071             }
14072             break;
14073         }
14074     }
14075 }
14076
14077 void
14078 ClockClick (int which)
14079 {       // [HGM] code moved to back-end from winboard.c
14080         if(which) { // black clock
14081           if (gameMode == EditPosition || gameMode == IcsExamining) {
14082             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14083             SetBlackToPlayEvent();
14084           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14085           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14086           } else if (shiftKey) {
14087             AdjustClock(which, -1);
14088           } else if (gameMode == IcsPlayingWhite ||
14089                      gameMode == MachinePlaysBlack) {
14090             CallFlagEvent();
14091           }
14092         } else { // white clock
14093           if (gameMode == EditPosition || gameMode == IcsExamining) {
14094             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14095             SetWhiteToPlayEvent();
14096           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14097           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14098           } else if (shiftKey) {
14099             AdjustClock(which, -1);
14100           } else if (gameMode == IcsPlayingBlack ||
14101                    gameMode == MachinePlaysWhite) {
14102             CallFlagEvent();
14103           }
14104         }
14105 }
14106
14107 void
14108 DrawEvent ()
14109 {
14110     /* Offer draw or accept pending draw offer from opponent */
14111
14112     if (appData.icsActive) {
14113         /* Note: tournament rules require draw offers to be
14114            made after you make your move but before you punch
14115            your clock.  Currently ICS doesn't let you do that;
14116            instead, you immediately punch your clock after making
14117            a move, but you can offer a draw at any time. */
14118
14119         SendToICS(ics_prefix);
14120         SendToICS("draw\n");
14121         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14122     } else if (cmailMsgLoaded) {
14123         if (currentMove == cmailOldMove &&
14124             commentList[cmailOldMove] != NULL &&
14125             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14126                    "Black offers a draw" : "White offers a draw")) {
14127             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14128             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14129         } else if (currentMove == cmailOldMove + 1) {
14130             char *offer = WhiteOnMove(cmailOldMove) ?
14131               "White offers a draw" : "Black offers a draw";
14132             AppendComment(currentMove, offer, TRUE);
14133             DisplayComment(currentMove - 1, offer);
14134             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14135         } else {
14136             DisplayError(_("You must make your move before offering a draw"), 0);
14137             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14138         }
14139     } else if (first.offeredDraw) {
14140         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14141     } else {
14142         if (first.sendDrawOffers) {
14143             SendToProgram("draw\n", &first);
14144             userOfferedDraw = TRUE;
14145         }
14146     }
14147 }
14148
14149 void
14150 AdjournEvent ()
14151 {
14152     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14153
14154     if (appData.icsActive) {
14155         SendToICS(ics_prefix);
14156         SendToICS("adjourn\n");
14157     } else {
14158         /* Currently GNU Chess doesn't offer or accept Adjourns */
14159     }
14160 }
14161
14162
14163 void
14164 AbortEvent ()
14165 {
14166     /* Offer Abort or accept pending Abort offer from opponent */
14167
14168     if (appData.icsActive) {
14169         SendToICS(ics_prefix);
14170         SendToICS("abort\n");
14171     } else {
14172         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14173     }
14174 }
14175
14176 void
14177 ResignEvent ()
14178 {
14179     /* Resign.  You can do this even if it's not your turn. */
14180
14181     if (appData.icsActive) {
14182         SendToICS(ics_prefix);
14183         SendToICS("resign\n");
14184     } else {
14185         switch (gameMode) {
14186           case MachinePlaysWhite:
14187             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14188             break;
14189           case MachinePlaysBlack:
14190             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14191             break;
14192           case EditGame:
14193             if (cmailMsgLoaded) {
14194                 TruncateGame();
14195                 if (WhiteOnMove(cmailOldMove)) {
14196                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14197                 } else {
14198                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14199                 }
14200                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14201             }
14202             break;
14203           default:
14204             break;
14205         }
14206     }
14207 }
14208
14209
14210 void
14211 StopObservingEvent ()
14212 {
14213     /* Stop observing current games */
14214     SendToICS(ics_prefix);
14215     SendToICS("unobserve\n");
14216 }
14217
14218 void
14219 StopExaminingEvent ()
14220 {
14221     /* Stop observing current game */
14222     SendToICS(ics_prefix);
14223     SendToICS("unexamine\n");
14224 }
14225
14226 void
14227 ForwardInner (int target)
14228 {
14229     int limit;
14230
14231     if (appData.debugMode)
14232         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14233                 target, currentMove, forwardMostMove);
14234
14235     if (gameMode == EditPosition)
14236       return;
14237
14238     seekGraphUp = FALSE;
14239     MarkTargetSquares(1);
14240
14241     if (gameMode == PlayFromGameFile && !pausing)
14242       PauseEvent();
14243
14244     if (gameMode == IcsExamining && pausing)
14245       limit = pauseExamForwardMostMove;
14246     else
14247       limit = forwardMostMove;
14248
14249     if (target > limit) target = limit;
14250
14251     if (target > 0 && moveList[target - 1][0]) {
14252         int fromX, fromY, toX, toY;
14253         toX = moveList[target - 1][2] - AAA;
14254         toY = moveList[target - 1][3] - ONE;
14255         if (moveList[target - 1][1] == '@') {
14256             if (appData.highlightLastMove) {
14257                 SetHighlights(-1, -1, toX, toY);
14258             }
14259         } else {
14260             fromX = moveList[target - 1][0] - AAA;
14261             fromY = moveList[target - 1][1] - ONE;
14262             if (target == currentMove + 1) {
14263                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14264             }
14265             if (appData.highlightLastMove) {
14266                 SetHighlights(fromX, fromY, toX, toY);
14267             }
14268         }
14269     }
14270     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14271         gameMode == Training || gameMode == PlayFromGameFile ||
14272         gameMode == AnalyzeFile) {
14273         while (currentMove < target) {
14274             SendMoveToProgram(currentMove++, &first);
14275         }
14276     } else {
14277         currentMove = target;
14278     }
14279
14280     if (gameMode == EditGame || gameMode == EndOfGame) {
14281         whiteTimeRemaining = timeRemaining[0][currentMove];
14282         blackTimeRemaining = timeRemaining[1][currentMove];
14283     }
14284     DisplayBothClocks();
14285     DisplayMove(currentMove - 1);
14286     DrawPosition(FALSE, boards[currentMove]);
14287     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14288     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14289         DisplayComment(currentMove - 1, commentList[currentMove]);
14290     }
14291 }
14292
14293
14294 void
14295 ForwardEvent ()
14296 {
14297     if (gameMode == IcsExamining && !pausing) {
14298         SendToICS(ics_prefix);
14299         SendToICS("forward\n");
14300     } else {
14301         ForwardInner(currentMove + 1);
14302     }
14303 }
14304
14305 void
14306 ToEndEvent ()
14307 {
14308     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14309         /* to optimze, we temporarily turn off analysis mode while we feed
14310          * the remaining moves to the engine. Otherwise we get analysis output
14311          * after each move.
14312          */
14313         if (first.analysisSupport) {
14314           SendToProgram("exit\nforce\n", &first);
14315           first.analyzing = FALSE;
14316         }
14317     }
14318
14319     if (gameMode == IcsExamining && !pausing) {
14320         SendToICS(ics_prefix);
14321         SendToICS("forward 999999\n");
14322     } else {
14323         ForwardInner(forwardMostMove);
14324     }
14325
14326     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14327         /* we have fed all the moves, so reactivate analysis mode */
14328         SendToProgram("analyze\n", &first);
14329         first.analyzing = TRUE;
14330         /*first.maybeThinking = TRUE;*/
14331         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14332     }
14333 }
14334
14335 void
14336 BackwardInner (int target)
14337 {
14338     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14339
14340     if (appData.debugMode)
14341         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14342                 target, currentMove, forwardMostMove);
14343
14344     if (gameMode == EditPosition) return;
14345     seekGraphUp = FALSE;
14346     MarkTargetSquares(1);
14347     if (currentMove <= backwardMostMove) {
14348         ClearHighlights();
14349         DrawPosition(full_redraw, boards[currentMove]);
14350         return;
14351     }
14352     if (gameMode == PlayFromGameFile && !pausing)
14353       PauseEvent();
14354
14355     if (moveList[target][0]) {
14356         int fromX, fromY, toX, toY;
14357         toX = moveList[target][2] - AAA;
14358         toY = moveList[target][3] - ONE;
14359         if (moveList[target][1] == '@') {
14360             if (appData.highlightLastMove) {
14361                 SetHighlights(-1, -1, toX, toY);
14362             }
14363         } else {
14364             fromX = moveList[target][0] - AAA;
14365             fromY = moveList[target][1] - ONE;
14366             if (target == currentMove - 1) {
14367                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14368             }
14369             if (appData.highlightLastMove) {
14370                 SetHighlights(fromX, fromY, toX, toY);
14371             }
14372         }
14373     }
14374     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14375         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14376         while (currentMove > target) {
14377             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14378                 // null move cannot be undone. Reload program with move history before it.
14379                 int i;
14380                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14381                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14382                 }
14383                 SendBoard(&first, i); 
14384                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14385                 break;
14386             }
14387             SendToProgram("undo\n", &first);
14388             currentMove--;
14389         }
14390     } else {
14391         currentMove = target;
14392     }
14393
14394     if (gameMode == EditGame || gameMode == EndOfGame) {
14395         whiteTimeRemaining = timeRemaining[0][currentMove];
14396         blackTimeRemaining = timeRemaining[1][currentMove];
14397     }
14398     DisplayBothClocks();
14399     DisplayMove(currentMove - 1);
14400     DrawPosition(full_redraw, boards[currentMove]);
14401     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14402     // [HGM] PV info: routine tests if comment empty
14403     DisplayComment(currentMove - 1, commentList[currentMove]);
14404 }
14405
14406 void
14407 BackwardEvent ()
14408 {
14409     if (gameMode == IcsExamining && !pausing) {
14410         SendToICS(ics_prefix);
14411         SendToICS("backward\n");
14412     } else {
14413         BackwardInner(currentMove - 1);
14414     }
14415 }
14416
14417 void
14418 ToStartEvent ()
14419 {
14420     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14421         /* to optimize, we temporarily turn off analysis mode while we undo
14422          * all the moves. Otherwise we get analysis output after each undo.
14423          */
14424         if (first.analysisSupport) {
14425           SendToProgram("exit\nforce\n", &first);
14426           first.analyzing = FALSE;
14427         }
14428     }
14429
14430     if (gameMode == IcsExamining && !pausing) {
14431         SendToICS(ics_prefix);
14432         SendToICS("backward 999999\n");
14433     } else {
14434         BackwardInner(backwardMostMove);
14435     }
14436
14437     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14438         /* we have fed all the moves, so reactivate analysis mode */
14439         SendToProgram("analyze\n", &first);
14440         first.analyzing = TRUE;
14441         /*first.maybeThinking = TRUE;*/
14442         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14443     }
14444 }
14445
14446 void
14447 ToNrEvent (int to)
14448 {
14449   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14450   if (to >= forwardMostMove) to = forwardMostMove;
14451   if (to <= backwardMostMove) to = backwardMostMove;
14452   if (to < currentMove) {
14453     BackwardInner(to);
14454   } else {
14455     ForwardInner(to);
14456   }
14457 }
14458
14459 void
14460 RevertEvent (Boolean annotate)
14461 {
14462     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14463         return;
14464     }
14465     if (gameMode != IcsExamining) {
14466         DisplayError(_("You are not examining a game"), 0);
14467         return;
14468     }
14469     if (pausing) {
14470         DisplayError(_("You can't revert while pausing"), 0);
14471         return;
14472     }
14473     SendToICS(ics_prefix);
14474     SendToICS("revert\n");
14475 }
14476
14477 void
14478 RetractMoveEvent ()
14479 {
14480     switch (gameMode) {
14481       case MachinePlaysWhite:
14482       case MachinePlaysBlack:
14483         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14484             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14485             return;
14486         }
14487         if (forwardMostMove < 2) return;
14488         currentMove = forwardMostMove = forwardMostMove - 2;
14489         whiteTimeRemaining = timeRemaining[0][currentMove];
14490         blackTimeRemaining = timeRemaining[1][currentMove];
14491         DisplayBothClocks();
14492         DisplayMove(currentMove - 1);
14493         ClearHighlights();/*!! could figure this out*/
14494         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14495         SendToProgram("remove\n", &first);
14496         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14497         break;
14498
14499       case BeginningOfGame:
14500       default:
14501         break;
14502
14503       case IcsPlayingWhite:
14504       case IcsPlayingBlack:
14505         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14506             SendToICS(ics_prefix);
14507             SendToICS("takeback 2\n");
14508         } else {
14509             SendToICS(ics_prefix);
14510             SendToICS("takeback 1\n");
14511         }
14512         break;
14513     }
14514 }
14515
14516 void
14517 MoveNowEvent ()
14518 {
14519     ChessProgramState *cps;
14520
14521     switch (gameMode) {
14522       case MachinePlaysWhite:
14523         if (!WhiteOnMove(forwardMostMove)) {
14524             DisplayError(_("It is your turn"), 0);
14525             return;
14526         }
14527         cps = &first;
14528         break;
14529       case MachinePlaysBlack:
14530         if (WhiteOnMove(forwardMostMove)) {
14531             DisplayError(_("It is your turn"), 0);
14532             return;
14533         }
14534         cps = &first;
14535         break;
14536       case TwoMachinesPlay:
14537         if (WhiteOnMove(forwardMostMove) ==
14538             (first.twoMachinesColor[0] == 'w')) {
14539             cps = &first;
14540         } else {
14541             cps = &second;
14542         }
14543         break;
14544       case BeginningOfGame:
14545       default:
14546         return;
14547     }
14548     SendToProgram("?\n", cps);
14549 }
14550
14551 void
14552 TruncateGameEvent ()
14553 {
14554     EditGameEvent();
14555     if (gameMode != EditGame) return;
14556     TruncateGame();
14557 }
14558
14559 void
14560 TruncateGame ()
14561 {
14562     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14563     if (forwardMostMove > currentMove) {
14564         if (gameInfo.resultDetails != NULL) {
14565             free(gameInfo.resultDetails);
14566             gameInfo.resultDetails = NULL;
14567             gameInfo.result = GameUnfinished;
14568         }
14569         forwardMostMove = currentMove;
14570         HistorySet(parseList, backwardMostMove, forwardMostMove,
14571                    currentMove-1);
14572     }
14573 }
14574
14575 void
14576 HintEvent ()
14577 {
14578     if (appData.noChessProgram) return;
14579     switch (gameMode) {
14580       case MachinePlaysWhite:
14581         if (WhiteOnMove(forwardMostMove)) {
14582             DisplayError(_("Wait until your turn"), 0);
14583             return;
14584         }
14585         break;
14586       case BeginningOfGame:
14587       case MachinePlaysBlack:
14588         if (!WhiteOnMove(forwardMostMove)) {
14589             DisplayError(_("Wait until your turn"), 0);
14590             return;
14591         }
14592         break;
14593       default:
14594         DisplayError(_("No hint available"), 0);
14595         return;
14596     }
14597     SendToProgram("hint\n", &first);
14598     hintRequested = TRUE;
14599 }
14600
14601 void
14602 BookEvent ()
14603 {
14604     if (appData.noChessProgram) return;
14605     switch (gameMode) {
14606       case MachinePlaysWhite:
14607         if (WhiteOnMove(forwardMostMove)) {
14608             DisplayError(_("Wait until your turn"), 0);
14609             return;
14610         }
14611         break;
14612       case BeginningOfGame:
14613       case MachinePlaysBlack:
14614         if (!WhiteOnMove(forwardMostMove)) {
14615             DisplayError(_("Wait until your turn"), 0);
14616             return;
14617         }
14618         break;
14619       case EditPosition:
14620         EditPositionDone(TRUE);
14621         break;
14622       case TwoMachinesPlay:
14623         return;
14624       default:
14625         break;
14626     }
14627     SendToProgram("bk\n", &first);
14628     bookOutput[0] = NULLCHAR;
14629     bookRequested = TRUE;
14630 }
14631
14632 void
14633 AboutGameEvent ()
14634 {
14635     char *tags = PGNTags(&gameInfo);
14636     TagsPopUp(tags, CmailMsg());
14637     free(tags);
14638 }
14639
14640 /* end button procedures */
14641
14642 void
14643 PrintPosition (FILE *fp, int move)
14644 {
14645     int i, j;
14646
14647     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14648         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14649             char c = PieceToChar(boards[move][i][j]);
14650             fputc(c == 'x' ? '.' : c, fp);
14651             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14652         }
14653     }
14654     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14655       fprintf(fp, "white to play\n");
14656     else
14657       fprintf(fp, "black to play\n");
14658 }
14659
14660 void
14661 PrintOpponents (FILE *fp)
14662 {
14663     if (gameInfo.white != NULL) {
14664         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14665     } else {
14666         fprintf(fp, "\n");
14667     }
14668 }
14669
14670 /* Find last component of program's own name, using some heuristics */
14671 void
14672 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14673 {
14674     char *p, *q, c;
14675     int local = (strcmp(host, "localhost") == 0);
14676     while (!local && (p = strchr(prog, ';')) != NULL) {
14677         p++;
14678         while (*p == ' ') p++;
14679         prog = p;
14680     }
14681     if (*prog == '"' || *prog == '\'') {
14682         q = strchr(prog + 1, *prog);
14683     } else {
14684         q = strchr(prog, ' ');
14685     }
14686     if (q == NULL) q = prog + strlen(prog);
14687     p = q;
14688     while (p >= prog && *p != '/' && *p != '\\') p--;
14689     p++;
14690     if(p == prog && *p == '"') p++;
14691     c = *q; *q = 0;
14692     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14693     memcpy(buf, p, q - p);
14694     buf[q - p] = NULLCHAR;
14695     if (!local) {
14696         strcat(buf, "@");
14697         strcat(buf, host);
14698     }
14699 }
14700
14701 char *
14702 TimeControlTagValue ()
14703 {
14704     char buf[MSG_SIZ];
14705     if (!appData.clockMode) {
14706       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14707     } else if (movesPerSession > 0) {
14708       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14709     } else if (timeIncrement == 0) {
14710       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14711     } else {
14712       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14713     }
14714     return StrSave(buf);
14715 }
14716
14717 void
14718 SetGameInfo ()
14719 {
14720     /* This routine is used only for certain modes */
14721     VariantClass v = gameInfo.variant;
14722     ChessMove r = GameUnfinished;
14723     char *p = NULL;
14724
14725     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14726         r = gameInfo.result;
14727         p = gameInfo.resultDetails;
14728         gameInfo.resultDetails = NULL;
14729     }
14730     ClearGameInfo(&gameInfo);
14731     gameInfo.variant = v;
14732
14733     switch (gameMode) {
14734       case MachinePlaysWhite:
14735         gameInfo.event = StrSave( appData.pgnEventHeader );
14736         gameInfo.site = StrSave(HostName());
14737         gameInfo.date = PGNDate();
14738         gameInfo.round = StrSave("-");
14739         gameInfo.white = StrSave(first.tidy);
14740         gameInfo.black = StrSave(UserName());
14741         gameInfo.timeControl = TimeControlTagValue();
14742         break;
14743
14744       case MachinePlaysBlack:
14745         gameInfo.event = StrSave( appData.pgnEventHeader );
14746         gameInfo.site = StrSave(HostName());
14747         gameInfo.date = PGNDate();
14748         gameInfo.round = StrSave("-");
14749         gameInfo.white = StrSave(UserName());
14750         gameInfo.black = StrSave(first.tidy);
14751         gameInfo.timeControl = TimeControlTagValue();
14752         break;
14753
14754       case TwoMachinesPlay:
14755         gameInfo.event = StrSave( appData.pgnEventHeader );
14756         gameInfo.site = StrSave(HostName());
14757         gameInfo.date = PGNDate();
14758         if (roundNr > 0) {
14759             char buf[MSG_SIZ];
14760             snprintf(buf, MSG_SIZ, "%d", roundNr);
14761             gameInfo.round = StrSave(buf);
14762         } else {
14763             gameInfo.round = StrSave("-");
14764         }
14765         if (first.twoMachinesColor[0] == 'w') {
14766             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14767             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14768         } else {
14769             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14770             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14771         }
14772         gameInfo.timeControl = TimeControlTagValue();
14773         break;
14774
14775       case EditGame:
14776         gameInfo.event = StrSave("Edited game");
14777         gameInfo.site = StrSave(HostName());
14778         gameInfo.date = PGNDate();
14779         gameInfo.round = StrSave("-");
14780         gameInfo.white = StrSave("-");
14781         gameInfo.black = StrSave("-");
14782         gameInfo.result = r;
14783         gameInfo.resultDetails = p;
14784         break;
14785
14786       case EditPosition:
14787         gameInfo.event = StrSave("Edited position");
14788         gameInfo.site = StrSave(HostName());
14789         gameInfo.date = PGNDate();
14790         gameInfo.round = StrSave("-");
14791         gameInfo.white = StrSave("-");
14792         gameInfo.black = StrSave("-");
14793         break;
14794
14795       case IcsPlayingWhite:
14796       case IcsPlayingBlack:
14797       case IcsObserving:
14798       case IcsExamining:
14799         break;
14800
14801       case PlayFromGameFile:
14802         gameInfo.event = StrSave("Game from non-PGN file");
14803         gameInfo.site = StrSave(HostName());
14804         gameInfo.date = PGNDate();
14805         gameInfo.round = StrSave("-");
14806         gameInfo.white = StrSave("?");
14807         gameInfo.black = StrSave("?");
14808         break;
14809
14810       default:
14811         break;
14812     }
14813 }
14814
14815 void
14816 ReplaceComment (int index, char *text)
14817 {
14818     int len;
14819     char *p;
14820     float score;
14821
14822     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14823        pvInfoList[index-1].depth == len &&
14824        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14825        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14826     while (*text == '\n') text++;
14827     len = strlen(text);
14828     while (len > 0 && text[len - 1] == '\n') len--;
14829
14830     if (commentList[index] != NULL)
14831       free(commentList[index]);
14832
14833     if (len == 0) {
14834         commentList[index] = NULL;
14835         return;
14836     }
14837   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14838       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14839       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14840     commentList[index] = (char *) malloc(len + 2);
14841     strncpy(commentList[index], text, len);
14842     commentList[index][len] = '\n';
14843     commentList[index][len + 1] = NULLCHAR;
14844   } else {
14845     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14846     char *p;
14847     commentList[index] = (char *) malloc(len + 7);
14848     safeStrCpy(commentList[index], "{\n", 3);
14849     safeStrCpy(commentList[index]+2, text, len+1);
14850     commentList[index][len+2] = NULLCHAR;
14851     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14852     strcat(commentList[index], "\n}\n");
14853   }
14854 }
14855
14856 void
14857 CrushCRs (char *text)
14858 {
14859   char *p = text;
14860   char *q = text;
14861   char ch;
14862
14863   do {
14864     ch = *p++;
14865     if (ch == '\r') continue;
14866     *q++ = ch;
14867   } while (ch != '\0');
14868 }
14869
14870 void
14871 AppendComment (int index, char *text, Boolean addBraces)
14872 /* addBraces  tells if we should add {} */
14873 {
14874     int oldlen, len;
14875     char *old;
14876
14877 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14878     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14879
14880     CrushCRs(text);
14881     while (*text == '\n') text++;
14882     len = strlen(text);
14883     while (len > 0 && text[len - 1] == '\n') len--;
14884     text[len] = NULLCHAR;
14885
14886     if (len == 0) return;
14887
14888     if (commentList[index] != NULL) {
14889       Boolean addClosingBrace = addBraces;
14890         old = commentList[index];
14891         oldlen = strlen(old);
14892         while(commentList[index][oldlen-1] ==  '\n')
14893           commentList[index][--oldlen] = NULLCHAR;
14894         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14895         safeStrCpy(commentList[index], old, oldlen + len + 6);
14896         free(old);
14897         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14898         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14899           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14900           while (*text == '\n') { text++; len--; }
14901           commentList[index][--oldlen] = NULLCHAR;
14902       }
14903         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14904         else          strcat(commentList[index], "\n");
14905         strcat(commentList[index], text);
14906         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14907         else          strcat(commentList[index], "\n");
14908     } else {
14909         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14910         if(addBraces)
14911           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14912         else commentList[index][0] = NULLCHAR;
14913         strcat(commentList[index], text);
14914         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14915         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14916     }
14917 }
14918
14919 static char *
14920 FindStr (char * text, char * sub_text)
14921 {
14922     char * result = strstr( text, sub_text );
14923
14924     if( result != NULL ) {
14925         result += strlen( sub_text );
14926     }
14927
14928     return result;
14929 }
14930
14931 /* [AS] Try to extract PV info from PGN comment */
14932 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14933 char *
14934 GetInfoFromComment (int index, char * text)
14935 {
14936     char * sep = text, *p;
14937
14938     if( text != NULL && index > 0 ) {
14939         int score = 0;
14940         int depth = 0;
14941         int time = -1, sec = 0, deci;
14942         char * s_eval = FindStr( text, "[%eval " );
14943         char * s_emt = FindStr( text, "[%emt " );
14944
14945         if( s_eval != NULL || s_emt != NULL ) {
14946             /* New style */
14947             char delim;
14948
14949             if( s_eval != NULL ) {
14950                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14951                     return text;
14952                 }
14953
14954                 if( delim != ']' ) {
14955                     return text;
14956                 }
14957             }
14958
14959             if( s_emt != NULL ) {
14960             }
14961                 return text;
14962         }
14963         else {
14964             /* We expect something like: [+|-]nnn.nn/dd */
14965             int score_lo = 0;
14966
14967             if(*text != '{') return text; // [HGM] braces: must be normal comment
14968
14969             sep = strchr( text, '/' );
14970             if( sep == NULL || sep < (text+4) ) {
14971                 return text;
14972             }
14973
14974             p = text;
14975             if(p[1] == '(') { // comment starts with PV
14976                p = strchr(p, ')'); // locate end of PV
14977                if(p == NULL || sep < p+5) return text;
14978                // at this point we have something like "{(.*) +0.23/6 ..."
14979                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14980                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14981                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14982             }
14983             time = -1; sec = -1; deci = -1;
14984             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14985                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14986                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14987                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14988                 return text;
14989             }
14990
14991             if( score_lo < 0 || score_lo >= 100 ) {
14992                 return text;
14993             }
14994
14995             if(sec >= 0) time = 600*time + 10*sec; else
14996             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14997
14998             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14999
15000             /* [HGM] PV time: now locate end of PV info */
15001             while( *++sep >= '0' && *sep <= '9'); // strip depth
15002             if(time >= 0)
15003             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15004             if(sec >= 0)
15005             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15006             if(deci >= 0)
15007             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15008             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15009         }
15010
15011         if( depth <= 0 ) {
15012             return text;
15013         }
15014
15015         if( time < 0 ) {
15016             time = -1;
15017         }
15018
15019         pvInfoList[index-1].depth = depth;
15020         pvInfoList[index-1].score = score;
15021         pvInfoList[index-1].time  = 10*time; // centi-sec
15022         if(*sep == '}') *sep = 0; else *--sep = '{';
15023         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15024     }
15025     return sep;
15026 }
15027
15028 void
15029 SendToProgram (char *message, ChessProgramState *cps)
15030 {
15031     int count, outCount, error;
15032     char buf[MSG_SIZ];
15033
15034     if (cps->pr == NoProc) return;
15035     Attention(cps);
15036
15037     if (appData.debugMode) {
15038         TimeMark now;
15039         GetTimeMark(&now);
15040         fprintf(debugFP, "%ld >%-6s: %s",
15041                 SubtractTimeMarks(&now, &programStartTime),
15042                 cps->which, message);
15043     }
15044
15045     count = strlen(message);
15046     outCount = OutputToProcess(cps->pr, message, count, &error);
15047     if (outCount < count && !exiting
15048                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15049       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15050       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15051         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15052             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15053                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15054                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15055                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15056             } else {
15057                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15058                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15059                 gameInfo.result = res;
15060             }
15061             gameInfo.resultDetails = StrSave(buf);
15062         }
15063         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15064         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15065     }
15066 }
15067
15068 void
15069 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15070 {
15071     char *end_str;
15072     char buf[MSG_SIZ];
15073     ChessProgramState *cps = (ChessProgramState *)closure;
15074
15075     if (isr != cps->isr) return; /* Killed intentionally */
15076     if (count <= 0) {
15077         if (count == 0) {
15078             RemoveInputSource(cps->isr);
15079             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15080             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15081                     _(cps->which), cps->program);
15082         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15083                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15084                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15085                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15086                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15087                 } else {
15088                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15089                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15090                     gameInfo.result = res;
15091                 }
15092                 gameInfo.resultDetails = StrSave(buf);
15093             }
15094             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15095             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15096         } else {
15097             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15098                     _(cps->which), cps->program);
15099             RemoveInputSource(cps->isr);
15100
15101             /* [AS] Program is misbehaving badly... kill it */
15102             if( count == -2 ) {
15103                 DestroyChildProcess( cps->pr, 9 );
15104                 cps->pr = NoProc;
15105             }
15106
15107             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15108         }
15109         return;
15110     }
15111
15112     if ((end_str = strchr(message, '\r')) != NULL)
15113       *end_str = NULLCHAR;
15114     if ((end_str = strchr(message, '\n')) != NULL)
15115       *end_str = NULLCHAR;
15116
15117     if (appData.debugMode) {
15118         TimeMark now; int print = 1;
15119         char *quote = ""; char c; int i;
15120
15121         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15122                 char start = message[0];
15123                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15124                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15125                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15126                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15127                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15128                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15129                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15130                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15131                    sscanf(message, "hint: %c", &c)!=1 && 
15132                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15133                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15134                     print = (appData.engineComments >= 2);
15135                 }
15136                 message[0] = start; // restore original message
15137         }
15138         if(print) {
15139                 GetTimeMark(&now);
15140                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15141                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15142                         quote,
15143                         message);
15144         }
15145     }
15146
15147     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15148     if (appData.icsEngineAnalyze) {
15149         if (strstr(message, "whisper") != NULL ||
15150              strstr(message, "kibitz") != NULL ||
15151             strstr(message, "tellics") != NULL) return;
15152     }
15153
15154     HandleMachineMove(message, cps);
15155 }
15156
15157
15158 void
15159 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15160 {
15161     char buf[MSG_SIZ];
15162     int seconds;
15163
15164     if( timeControl_2 > 0 ) {
15165         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15166             tc = timeControl_2;
15167         }
15168     }
15169     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15170     inc /= cps->timeOdds;
15171     st  /= cps->timeOdds;
15172
15173     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15174
15175     if (st > 0) {
15176       /* Set exact time per move, normally using st command */
15177       if (cps->stKludge) {
15178         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15179         seconds = st % 60;
15180         if (seconds == 0) {
15181           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15182         } else {
15183           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15184         }
15185       } else {
15186         snprintf(buf, MSG_SIZ, "st %d\n", st);
15187       }
15188     } else {
15189       /* Set conventional or incremental time control, using level command */
15190       if (seconds == 0) {
15191         /* Note old gnuchess bug -- minutes:seconds used to not work.
15192            Fixed in later versions, but still avoid :seconds
15193            when seconds is 0. */
15194         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15195       } else {
15196         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15197                  seconds, inc/1000.);
15198       }
15199     }
15200     SendToProgram(buf, cps);
15201
15202     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15203     /* Orthogonally, limit search to given depth */
15204     if (sd > 0) {
15205       if (cps->sdKludge) {
15206         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15207       } else {
15208         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15209       }
15210       SendToProgram(buf, cps);
15211     }
15212
15213     if(cps->nps >= 0) { /* [HGM] nps */
15214         if(cps->supportsNPS == FALSE)
15215           cps->nps = -1; // don't use if engine explicitly says not supported!
15216         else {
15217           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15218           SendToProgram(buf, cps);
15219         }
15220     }
15221 }
15222
15223 ChessProgramState *
15224 WhitePlayer ()
15225 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15226 {
15227     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15228        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15229         return &second;
15230     return &first;
15231 }
15232
15233 void
15234 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15235 {
15236     char message[MSG_SIZ];
15237     long time, otime;
15238
15239     /* Note: this routine must be called when the clocks are stopped
15240        or when they have *just* been set or switched; otherwise
15241        it will be off by the time since the current tick started.
15242     */
15243     if (machineWhite) {
15244         time = whiteTimeRemaining / 10;
15245         otime = blackTimeRemaining / 10;
15246     } else {
15247         time = blackTimeRemaining / 10;
15248         otime = whiteTimeRemaining / 10;
15249     }
15250     /* [HGM] translate opponent's time by time-odds factor */
15251     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15252     if (appData.debugMode) {
15253         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15254     }
15255
15256     if (time <= 0) time = 1;
15257     if (otime <= 0) otime = 1;
15258
15259     snprintf(message, MSG_SIZ, "time %ld\n", time);
15260     SendToProgram(message, cps);
15261
15262     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15263     SendToProgram(message, cps);
15264 }
15265
15266 int
15267 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15268 {
15269   char buf[MSG_SIZ];
15270   int len = strlen(name);
15271   int val;
15272
15273   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15274     (*p) += len + 1;
15275     sscanf(*p, "%d", &val);
15276     *loc = (val != 0);
15277     while (**p && **p != ' ')
15278       (*p)++;
15279     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15280     SendToProgram(buf, cps);
15281     return TRUE;
15282   }
15283   return FALSE;
15284 }
15285
15286 int
15287 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15288 {
15289   char buf[MSG_SIZ];
15290   int len = strlen(name);
15291   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15292     (*p) += len + 1;
15293     sscanf(*p, "%d", loc);
15294     while (**p && **p != ' ') (*p)++;
15295     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15296     SendToProgram(buf, cps);
15297     return TRUE;
15298   }
15299   return FALSE;
15300 }
15301
15302 int
15303 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15304 {
15305   char buf[MSG_SIZ];
15306   int len = strlen(name);
15307   if (strncmp((*p), name, len) == 0
15308       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15309     (*p) += len + 2;
15310     sscanf(*p, "%[^\"]", loc);
15311     while (**p && **p != '\"') (*p)++;
15312     if (**p == '\"') (*p)++;
15313     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15314     SendToProgram(buf, cps);
15315     return TRUE;
15316   }
15317   return FALSE;
15318 }
15319
15320 int
15321 ParseOption (Option *opt, ChessProgramState *cps)
15322 // [HGM] options: process the string that defines an engine option, and determine
15323 // name, type, default value, and allowed value range
15324 {
15325         char *p, *q, buf[MSG_SIZ];
15326         int n, min = (-1)<<31, max = 1<<31, def;
15327
15328         if(p = strstr(opt->name, " -spin ")) {
15329             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15330             if(max < min) max = min; // enforce consistency
15331             if(def < min) def = min;
15332             if(def > max) def = max;
15333             opt->value = def;
15334             opt->min = min;
15335             opt->max = max;
15336             opt->type = Spin;
15337         } else if((p = strstr(opt->name, " -slider "))) {
15338             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15339             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15340             if(max < min) max = min; // enforce consistency
15341             if(def < min) def = min;
15342             if(def > max) def = max;
15343             opt->value = def;
15344             opt->min = min;
15345             opt->max = max;
15346             opt->type = Spin; // Slider;
15347         } else if((p = strstr(opt->name, " -string "))) {
15348             opt->textValue = p+9;
15349             opt->type = TextBox;
15350         } else if((p = strstr(opt->name, " -file "))) {
15351             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15352             opt->textValue = p+7;
15353             opt->type = FileName; // FileName;
15354         } else if((p = strstr(opt->name, " -path "))) {
15355             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15356             opt->textValue = p+7;
15357             opt->type = PathName; // PathName;
15358         } else if(p = strstr(opt->name, " -check ")) {
15359             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15360             opt->value = (def != 0);
15361             opt->type = CheckBox;
15362         } else if(p = strstr(opt->name, " -combo ")) {
15363             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15364             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15365             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15366             opt->value = n = 0;
15367             while(q = StrStr(q, " /// ")) {
15368                 n++; *q = 0;    // count choices, and null-terminate each of them
15369                 q += 5;
15370                 if(*q == '*') { // remember default, which is marked with * prefix
15371                     q++;
15372                     opt->value = n;
15373                 }
15374                 cps->comboList[cps->comboCnt++] = q;
15375             }
15376             cps->comboList[cps->comboCnt++] = NULL;
15377             opt->max = n + 1;
15378             opt->type = ComboBox;
15379         } else if(p = strstr(opt->name, " -button")) {
15380             opt->type = Button;
15381         } else if(p = strstr(opt->name, " -save")) {
15382             opt->type = SaveButton;
15383         } else return FALSE;
15384         *p = 0; // terminate option name
15385         // now look if the command-line options define a setting for this engine option.
15386         if(cps->optionSettings && cps->optionSettings[0])
15387             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15388         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15389           snprintf(buf, MSG_SIZ, "option %s", p);
15390                 if(p = strstr(buf, ",")) *p = 0;
15391                 if(q = strchr(buf, '=')) switch(opt->type) {
15392                     case ComboBox:
15393                         for(n=0; n<opt->max; n++)
15394                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15395                         break;
15396                     case TextBox:
15397                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15398                         break;
15399                     case Spin:
15400                     case CheckBox:
15401                         opt->value = atoi(q+1);
15402                     default:
15403                         break;
15404                 }
15405                 strcat(buf, "\n");
15406                 SendToProgram(buf, cps);
15407         }
15408         return TRUE;
15409 }
15410
15411 void
15412 FeatureDone (ChessProgramState *cps, int val)
15413 {
15414   DelayedEventCallback cb = GetDelayedEvent();
15415   if ((cb == InitBackEnd3 && cps == &first) ||
15416       (cb == SettingsMenuIfReady && cps == &second) ||
15417       (cb == LoadEngine) ||
15418       (cb == TwoMachinesEventIfReady)) {
15419     CancelDelayedEvent();
15420     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15421   }
15422   cps->initDone = val;
15423 }
15424
15425 /* Parse feature command from engine */
15426 void
15427 ParseFeatures (char *args, ChessProgramState *cps)
15428 {
15429   char *p = args;
15430   char *q;
15431   int val;
15432   char buf[MSG_SIZ];
15433
15434   for (;;) {
15435     while (*p == ' ') p++;
15436     if (*p == NULLCHAR) return;
15437
15438     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15439     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15440     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15441     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15442     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15443     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15444     if (BoolFeature(&p, "reuse", &val, cps)) {
15445       /* Engine can disable reuse, but can't enable it if user said no */
15446       if (!val) cps->reuse = FALSE;
15447       continue;
15448     }
15449     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15450     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15451       if (gameMode == TwoMachinesPlay) {
15452         DisplayTwoMachinesTitle();
15453       } else {
15454         DisplayTitle("");
15455       }
15456       continue;
15457     }
15458     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15459     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15460     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15461     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15462     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15463     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15464     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15465     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15466     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15467     if (IntFeature(&p, "done", &val, cps)) {
15468       FeatureDone(cps, val);
15469       continue;
15470     }
15471     /* Added by Tord: */
15472     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15473     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15474     /* End of additions by Tord */
15475
15476     /* [HGM] added features: */
15477     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15478     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15479     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15480     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15481     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15482     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15483     if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15484         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15485           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15486             SendToProgram(buf, cps);
15487             continue;
15488         }
15489         if(cps->nrOptions >= MAX_OPTIONS) {
15490             cps->nrOptions--;
15491             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15492             DisplayError(buf, 0);
15493         }
15494         continue;
15495     }
15496     /* End of additions by HGM */
15497
15498     /* unknown feature: complain and skip */
15499     q = p;
15500     while (*q && *q != '=') q++;
15501     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15502     SendToProgram(buf, cps);
15503     p = q;
15504     if (*p == '=') {
15505       p++;
15506       if (*p == '\"') {
15507         p++;
15508         while (*p && *p != '\"') p++;
15509         if (*p == '\"') p++;
15510       } else {
15511         while (*p && *p != ' ') p++;
15512       }
15513     }
15514   }
15515
15516 }
15517
15518 void
15519 PeriodicUpdatesEvent (int newState)
15520 {
15521     if (newState == appData.periodicUpdates)
15522       return;
15523
15524     appData.periodicUpdates=newState;
15525
15526     /* Display type changes, so update it now */
15527 //    DisplayAnalysis();
15528
15529     /* Get the ball rolling again... */
15530     if (newState) {
15531         AnalysisPeriodicEvent(1);
15532         StartAnalysisClock();
15533     }
15534 }
15535
15536 void
15537 PonderNextMoveEvent (int newState)
15538 {
15539     if (newState == appData.ponderNextMove) return;
15540     if (gameMode == EditPosition) EditPositionDone(TRUE);
15541     if (newState) {
15542         SendToProgram("hard\n", &first);
15543         if (gameMode == TwoMachinesPlay) {
15544             SendToProgram("hard\n", &second);
15545         }
15546     } else {
15547         SendToProgram("easy\n", &first);
15548         thinkOutput[0] = NULLCHAR;
15549         if (gameMode == TwoMachinesPlay) {
15550             SendToProgram("easy\n", &second);
15551         }
15552     }
15553     appData.ponderNextMove = newState;
15554 }
15555
15556 void
15557 NewSettingEvent (int option, int *feature, char *command, int value)
15558 {
15559     char buf[MSG_SIZ];
15560
15561     if (gameMode == EditPosition) EditPositionDone(TRUE);
15562     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15563     if(feature == NULL || *feature) SendToProgram(buf, &first);
15564     if (gameMode == TwoMachinesPlay) {
15565         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15566     }
15567 }
15568
15569 void
15570 ShowThinkingEvent ()
15571 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15572 {
15573     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15574     int newState = appData.showThinking
15575         // [HGM] thinking: other features now need thinking output as well
15576         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15577
15578     if (oldState == newState) return;
15579     oldState = newState;
15580     if (gameMode == EditPosition) EditPositionDone(TRUE);
15581     if (oldState) {
15582         SendToProgram("post\n", &first);
15583         if (gameMode == TwoMachinesPlay) {
15584             SendToProgram("post\n", &second);
15585         }
15586     } else {
15587         SendToProgram("nopost\n", &first);
15588         thinkOutput[0] = NULLCHAR;
15589         if (gameMode == TwoMachinesPlay) {
15590             SendToProgram("nopost\n", &second);
15591         }
15592     }
15593 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15594 }
15595
15596 void
15597 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15598 {
15599   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15600   if (pr == NoProc) return;
15601   AskQuestion(title, question, replyPrefix, pr);
15602 }
15603
15604 void
15605 TypeInEvent (char firstChar)
15606 {
15607     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15608         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15609         gameMode == AnalyzeMode || gameMode == EditGame || 
15610         gameMode == EditPosition || gameMode == IcsExamining ||
15611         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15612         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15613                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15614                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15615         gameMode == Training) PopUpMoveDialog(firstChar);
15616 }
15617
15618 void
15619 TypeInDoneEvent (char *move)
15620 {
15621         Board board;
15622         int n, fromX, fromY, toX, toY;
15623         char promoChar;
15624         ChessMove moveType;
15625
15626         // [HGM] FENedit
15627         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15628                 EditPositionPasteFEN(move);
15629                 return;
15630         }
15631         // [HGM] movenum: allow move number to be typed in any mode
15632         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15633           ToNrEvent(2*n-1);
15634           return;
15635         }
15636
15637       if (gameMode != EditGame && currentMove != forwardMostMove && 
15638         gameMode != Training) {
15639         DisplayMoveError(_("Displayed move is not current"));
15640       } else {
15641         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15642           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15643         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15644         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15645           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15646           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15647         } else {
15648           DisplayMoveError(_("Could not parse move"));
15649         }
15650       }
15651 }
15652
15653 void
15654 DisplayMove (int moveNumber)
15655 {
15656     char message[MSG_SIZ];
15657     char res[MSG_SIZ];
15658     char cpThinkOutput[MSG_SIZ];
15659
15660     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15661
15662     if (moveNumber == forwardMostMove - 1 ||
15663         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15664
15665         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15666
15667         if (strchr(cpThinkOutput, '\n')) {
15668             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15669         }
15670     } else {
15671         *cpThinkOutput = NULLCHAR;
15672     }
15673
15674     /* [AS] Hide thinking from human user */
15675     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15676         *cpThinkOutput = NULLCHAR;
15677         if( thinkOutput[0] != NULLCHAR ) {
15678             int i;
15679
15680             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15681                 cpThinkOutput[i] = '.';
15682             }
15683             cpThinkOutput[i] = NULLCHAR;
15684             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15685         }
15686     }
15687
15688     if (moveNumber == forwardMostMove - 1 &&
15689         gameInfo.resultDetails != NULL) {
15690         if (gameInfo.resultDetails[0] == NULLCHAR) {
15691           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15692         } else {
15693           snprintf(res, MSG_SIZ, " {%s} %s",
15694                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15695         }
15696     } else {
15697         res[0] = NULLCHAR;
15698     }
15699
15700     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15701         DisplayMessage(res, cpThinkOutput);
15702     } else {
15703       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15704                 WhiteOnMove(moveNumber) ? " " : ".. ",
15705                 parseList[moveNumber], res);
15706         DisplayMessage(message, cpThinkOutput);
15707     }
15708 }
15709
15710 void
15711 DisplayComment (int moveNumber, char *text)
15712 {
15713     char title[MSG_SIZ];
15714
15715     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15716       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15717     } else {
15718       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15719               WhiteOnMove(moveNumber) ? " " : ".. ",
15720               parseList[moveNumber]);
15721     }
15722     if (text != NULL && (appData.autoDisplayComment || commentUp))
15723         CommentPopUp(title, text);
15724 }
15725
15726 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15727  * might be busy thinking or pondering.  It can be omitted if your
15728  * gnuchess is configured to stop thinking immediately on any user
15729  * input.  However, that gnuchess feature depends on the FIONREAD
15730  * ioctl, which does not work properly on some flavors of Unix.
15731  */
15732 void
15733 Attention (ChessProgramState *cps)
15734 {
15735 #if ATTENTION
15736     if (!cps->useSigint) return;
15737     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15738     switch (gameMode) {
15739       case MachinePlaysWhite:
15740       case MachinePlaysBlack:
15741       case TwoMachinesPlay:
15742       case IcsPlayingWhite:
15743       case IcsPlayingBlack:
15744       case AnalyzeMode:
15745       case AnalyzeFile:
15746         /* Skip if we know it isn't thinking */
15747         if (!cps->maybeThinking) return;
15748         if (appData.debugMode)
15749           fprintf(debugFP, "Interrupting %s\n", cps->which);
15750         InterruptChildProcess(cps->pr);
15751         cps->maybeThinking = FALSE;
15752         break;
15753       default:
15754         break;
15755     }
15756 #endif /*ATTENTION*/
15757 }
15758
15759 int
15760 CheckFlags ()
15761 {
15762     if (whiteTimeRemaining <= 0) {
15763         if (!whiteFlag) {
15764             whiteFlag = TRUE;
15765             if (appData.icsActive) {
15766                 if (appData.autoCallFlag &&
15767                     gameMode == IcsPlayingBlack && !blackFlag) {
15768                   SendToICS(ics_prefix);
15769                   SendToICS("flag\n");
15770                 }
15771             } else {
15772                 if (blackFlag) {
15773                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15774                 } else {
15775                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15776                     if (appData.autoCallFlag) {
15777                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15778                         return TRUE;
15779                     }
15780                 }
15781             }
15782         }
15783     }
15784     if (blackTimeRemaining <= 0) {
15785         if (!blackFlag) {
15786             blackFlag = TRUE;
15787             if (appData.icsActive) {
15788                 if (appData.autoCallFlag &&
15789                     gameMode == IcsPlayingWhite && !whiteFlag) {
15790                   SendToICS(ics_prefix);
15791                   SendToICS("flag\n");
15792                 }
15793             } else {
15794                 if (whiteFlag) {
15795                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15796                 } else {
15797                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15798                     if (appData.autoCallFlag) {
15799                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15800                         return TRUE;
15801                     }
15802                 }
15803             }
15804         }
15805     }
15806     return FALSE;
15807 }
15808
15809 void
15810 CheckTimeControl ()
15811 {
15812     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15813         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15814
15815     /*
15816      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15817      */
15818     if ( !WhiteOnMove(forwardMostMove) ) {
15819         /* White made time control */
15820         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15821         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15822         /* [HGM] time odds: correct new time quota for time odds! */
15823                                             / WhitePlayer()->timeOdds;
15824         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15825     } else {
15826         lastBlack -= blackTimeRemaining;
15827         /* Black made time control */
15828         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15829                                             / WhitePlayer()->other->timeOdds;
15830         lastWhite = whiteTimeRemaining;
15831     }
15832 }
15833
15834 void
15835 DisplayBothClocks ()
15836 {
15837     int wom = gameMode == EditPosition ?
15838       !blackPlaysFirst : WhiteOnMove(currentMove);
15839     DisplayWhiteClock(whiteTimeRemaining, wom);
15840     DisplayBlackClock(blackTimeRemaining, !wom);
15841 }
15842
15843
15844 /* Timekeeping seems to be a portability nightmare.  I think everyone
15845    has ftime(), but I'm really not sure, so I'm including some ifdefs
15846    to use other calls if you don't.  Clocks will be less accurate if
15847    you have neither ftime nor gettimeofday.
15848 */
15849
15850 /* VS 2008 requires the #include outside of the function */
15851 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15852 #include <sys/timeb.h>
15853 #endif
15854
15855 /* Get the current time as a TimeMark */
15856 void
15857 GetTimeMark (TimeMark *tm)
15858 {
15859 #if HAVE_GETTIMEOFDAY
15860
15861     struct timeval timeVal;
15862     struct timezone timeZone;
15863
15864     gettimeofday(&timeVal, &timeZone);
15865     tm->sec = (long) timeVal.tv_sec;
15866     tm->ms = (int) (timeVal.tv_usec / 1000L);
15867
15868 #else /*!HAVE_GETTIMEOFDAY*/
15869 #if HAVE_FTIME
15870
15871 // include <sys/timeb.h> / moved to just above start of function
15872     struct timeb timeB;
15873
15874     ftime(&timeB);
15875     tm->sec = (long) timeB.time;
15876     tm->ms = (int) timeB.millitm;
15877
15878 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15879     tm->sec = (long) time(NULL);
15880     tm->ms = 0;
15881 #endif
15882 #endif
15883 }
15884
15885 /* Return the difference in milliseconds between two
15886    time marks.  We assume the difference will fit in a long!
15887 */
15888 long
15889 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
15890 {
15891     return 1000L*(tm2->sec - tm1->sec) +
15892            (long) (tm2->ms - tm1->ms);
15893 }
15894
15895
15896 /*
15897  * Code to manage the game clocks.
15898  *
15899  * In tournament play, black starts the clock and then white makes a move.
15900  * We give the human user a slight advantage if he is playing white---the
15901  * clocks don't run until he makes his first move, so it takes zero time.
15902  * Also, we don't account for network lag, so we could get out of sync
15903  * with GNU Chess's clock -- but then, referees are always right.
15904  */
15905
15906 static TimeMark tickStartTM;
15907 static long intendedTickLength;
15908
15909 long
15910 NextTickLength (long timeRemaining)
15911 {
15912     long nominalTickLength, nextTickLength;
15913
15914     if (timeRemaining > 0L && timeRemaining <= 10000L)
15915       nominalTickLength = 100L;
15916     else
15917       nominalTickLength = 1000L;
15918     nextTickLength = timeRemaining % nominalTickLength;
15919     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15920
15921     return nextTickLength;
15922 }
15923
15924 /* Adjust clock one minute up or down */
15925 void
15926 AdjustClock (Boolean which, int dir)
15927 {
15928     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15929     if(which) blackTimeRemaining += 60000*dir;
15930     else      whiteTimeRemaining += 60000*dir;
15931     DisplayBothClocks();
15932     adjustedClock = TRUE;
15933 }
15934
15935 /* Stop clocks and reset to a fresh time control */
15936 void
15937 ResetClocks ()
15938 {
15939     (void) StopClockTimer();
15940     if (appData.icsActive) {
15941         whiteTimeRemaining = blackTimeRemaining = 0;
15942     } else if (searchTime) {
15943         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15944         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15945     } else { /* [HGM] correct new time quote for time odds */
15946         whiteTC = blackTC = fullTimeControlString;
15947         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15948         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15949     }
15950     if (whiteFlag || blackFlag) {
15951         DisplayTitle("");
15952         whiteFlag = blackFlag = FALSE;
15953     }
15954     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15955     DisplayBothClocks();
15956     adjustedClock = FALSE;
15957 }
15958
15959 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15960
15961 /* Decrement running clock by amount of time that has passed */
15962 void
15963 DecrementClocks ()
15964 {
15965     long timeRemaining;
15966     long lastTickLength, fudge;
15967     TimeMark now;
15968
15969     if (!appData.clockMode) return;
15970     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15971
15972     GetTimeMark(&now);
15973
15974     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15975
15976     /* Fudge if we woke up a little too soon */
15977     fudge = intendedTickLength - lastTickLength;
15978     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15979
15980     if (WhiteOnMove(forwardMostMove)) {
15981         if(whiteNPS >= 0) lastTickLength = 0;
15982         timeRemaining = whiteTimeRemaining -= lastTickLength;
15983         if(timeRemaining < 0 && !appData.icsActive) {
15984             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15985             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15986                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15987                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15988             }
15989         }
15990         DisplayWhiteClock(whiteTimeRemaining - fudge,
15991                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15992     } else {
15993         if(blackNPS >= 0) lastTickLength = 0;
15994         timeRemaining = blackTimeRemaining -= lastTickLength;
15995         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15996             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15997             if(suddenDeath) {
15998                 blackStartMove = forwardMostMove;
15999                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16000             }
16001         }
16002         DisplayBlackClock(blackTimeRemaining - fudge,
16003                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16004     }
16005     if (CheckFlags()) return;
16006
16007     tickStartTM = now;
16008     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16009     StartClockTimer(intendedTickLength);
16010
16011     /* if the time remaining has fallen below the alarm threshold, sound the
16012      * alarm. if the alarm has sounded and (due to a takeback or time control
16013      * with increment) the time remaining has increased to a level above the
16014      * threshold, reset the alarm so it can sound again.
16015      */
16016
16017     if (appData.icsActive && appData.icsAlarm) {
16018
16019         /* make sure we are dealing with the user's clock */
16020         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16021                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16022            )) return;
16023
16024         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16025             alarmSounded = FALSE;
16026         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16027             PlayAlarmSound();
16028             alarmSounded = TRUE;
16029         }
16030     }
16031 }
16032
16033
16034 /* A player has just moved, so stop the previously running
16035    clock and (if in clock mode) start the other one.
16036    We redisplay both clocks in case we're in ICS mode, because
16037    ICS gives us an update to both clocks after every move.
16038    Note that this routine is called *after* forwardMostMove
16039    is updated, so the last fractional tick must be subtracted
16040    from the color that is *not* on move now.
16041 */
16042 void
16043 SwitchClocks (int newMoveNr)
16044 {
16045     long lastTickLength;
16046     TimeMark now;
16047     int flagged = FALSE;
16048
16049     GetTimeMark(&now);
16050
16051     if (StopClockTimer() && appData.clockMode) {
16052         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16053         if (!WhiteOnMove(forwardMostMove)) {
16054             if(blackNPS >= 0) lastTickLength = 0;
16055             blackTimeRemaining -= lastTickLength;
16056            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16057 //         if(pvInfoList[forwardMostMove].time == -1)
16058                  pvInfoList[forwardMostMove].time =               // use GUI time
16059                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16060         } else {
16061            if(whiteNPS >= 0) lastTickLength = 0;
16062            whiteTimeRemaining -= lastTickLength;
16063            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16064 //         if(pvInfoList[forwardMostMove].time == -1)
16065                  pvInfoList[forwardMostMove].time =
16066                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16067         }
16068         flagged = CheckFlags();
16069     }
16070     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16071     CheckTimeControl();
16072
16073     if (flagged || !appData.clockMode) return;
16074
16075     switch (gameMode) {
16076       case MachinePlaysBlack:
16077       case MachinePlaysWhite:
16078       case BeginningOfGame:
16079         if (pausing) return;
16080         break;
16081
16082       case EditGame:
16083       case PlayFromGameFile:
16084       case IcsExamining:
16085         return;
16086
16087       default:
16088         break;
16089     }
16090
16091     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16092         if(WhiteOnMove(forwardMostMove))
16093              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16094         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16095     }
16096
16097     tickStartTM = now;
16098     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16099       whiteTimeRemaining : blackTimeRemaining);
16100     StartClockTimer(intendedTickLength);
16101 }
16102
16103
16104 /* Stop both clocks */
16105 void
16106 StopClocks ()
16107 {
16108     long lastTickLength;
16109     TimeMark now;
16110
16111     if (!StopClockTimer()) return;
16112     if (!appData.clockMode) return;
16113
16114     GetTimeMark(&now);
16115
16116     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16117     if (WhiteOnMove(forwardMostMove)) {
16118         if(whiteNPS >= 0) lastTickLength = 0;
16119         whiteTimeRemaining -= lastTickLength;
16120         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16121     } else {
16122         if(blackNPS >= 0) lastTickLength = 0;
16123         blackTimeRemaining -= lastTickLength;
16124         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16125     }
16126     CheckFlags();
16127 }
16128
16129 /* Start clock of player on move.  Time may have been reset, so
16130    if clock is already running, stop and restart it. */
16131 void
16132 StartClocks ()
16133 {
16134     (void) StopClockTimer(); /* in case it was running already */
16135     DisplayBothClocks();
16136     if (CheckFlags()) return;
16137
16138     if (!appData.clockMode) return;
16139     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16140
16141     GetTimeMark(&tickStartTM);
16142     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16143       whiteTimeRemaining : blackTimeRemaining);
16144
16145    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16146     whiteNPS = blackNPS = -1;
16147     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16148        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16149         whiteNPS = first.nps;
16150     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16151        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16152         blackNPS = first.nps;
16153     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16154         whiteNPS = second.nps;
16155     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16156         blackNPS = second.nps;
16157     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16158
16159     StartClockTimer(intendedTickLength);
16160 }
16161
16162 char *
16163 TimeString (long ms)
16164 {
16165     long second, minute, hour, day;
16166     char *sign = "";
16167     static char buf[32];
16168
16169     if (ms > 0 && ms <= 9900) {
16170       /* convert milliseconds to tenths, rounding up */
16171       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16172
16173       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16174       return buf;
16175     }
16176
16177     /* convert milliseconds to seconds, rounding up */
16178     /* use floating point to avoid strangeness of integer division
16179        with negative dividends on many machines */
16180     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16181
16182     if (second < 0) {
16183         sign = "-";
16184         second = -second;
16185     }
16186
16187     day = second / (60 * 60 * 24);
16188     second = second % (60 * 60 * 24);
16189     hour = second / (60 * 60);
16190     second = second % (60 * 60);
16191     minute = second / 60;
16192     second = second % 60;
16193
16194     if (day > 0)
16195       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16196               sign, day, hour, minute, second);
16197     else if (hour > 0)
16198       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16199     else
16200       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16201
16202     return buf;
16203 }
16204
16205
16206 /*
16207  * This is necessary because some C libraries aren't ANSI C compliant yet.
16208  */
16209 char *
16210 StrStr (char *string, char *match)
16211 {
16212     int i, length;
16213
16214     length = strlen(match);
16215
16216     for (i = strlen(string) - length; i >= 0; i--, string++)
16217       if (!strncmp(match, string, length))
16218         return string;
16219
16220     return NULL;
16221 }
16222
16223 char *
16224 StrCaseStr (char *string, char *match)
16225 {
16226     int i, j, length;
16227
16228     length = strlen(match);
16229
16230     for (i = strlen(string) - length; i >= 0; i--, string++) {
16231         for (j = 0; j < length; j++) {
16232             if (ToLower(match[j]) != ToLower(string[j]))
16233               break;
16234         }
16235         if (j == length) return string;
16236     }
16237
16238     return NULL;
16239 }
16240
16241 #ifndef _amigados
16242 int
16243 StrCaseCmp (char *s1, char *s2)
16244 {
16245     char c1, c2;
16246
16247     for (;;) {
16248         c1 = ToLower(*s1++);
16249         c2 = ToLower(*s2++);
16250         if (c1 > c2) return 1;
16251         if (c1 < c2) return -1;
16252         if (c1 == NULLCHAR) return 0;
16253     }
16254 }
16255
16256
16257 int
16258 ToLower (int c)
16259 {
16260     return isupper(c) ? tolower(c) : c;
16261 }
16262
16263
16264 int
16265 ToUpper (int c)
16266 {
16267     return islower(c) ? toupper(c) : c;
16268 }
16269 #endif /* !_amigados    */
16270
16271 char *
16272 StrSave (char *s)
16273 {
16274   char *ret;
16275
16276   if ((ret = (char *) malloc(strlen(s) + 1)))
16277     {
16278       safeStrCpy(ret, s, strlen(s)+1);
16279     }
16280   return ret;
16281 }
16282
16283 char *
16284 StrSavePtr (char *s, char **savePtr)
16285 {
16286     if (*savePtr) {
16287         free(*savePtr);
16288     }
16289     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16290       safeStrCpy(*savePtr, s, strlen(s)+1);
16291     }
16292     return(*savePtr);
16293 }
16294
16295 char *
16296 PGNDate ()
16297 {
16298     time_t clock;
16299     struct tm *tm;
16300     char buf[MSG_SIZ];
16301
16302     clock = time((time_t *)NULL);
16303     tm = localtime(&clock);
16304     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16305             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16306     return StrSave(buf);
16307 }
16308
16309
16310 char *
16311 PositionToFEN (int move, char *overrideCastling)
16312 {
16313     int i, j, fromX, fromY, toX, toY;
16314     int whiteToPlay;
16315     char buf[MSG_SIZ];
16316     char *p, *q;
16317     int emptycount;
16318     ChessSquare piece;
16319
16320     whiteToPlay = (gameMode == EditPosition) ?
16321       !blackPlaysFirst : (move % 2 == 0);
16322     p = buf;
16323
16324     /* Piece placement data */
16325     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16326         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16327         emptycount = 0;
16328         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16329             if (boards[move][i][j] == EmptySquare) {
16330                 emptycount++;
16331             } else { ChessSquare piece = boards[move][i][j];
16332                 if (emptycount > 0) {
16333                     if(emptycount<10) /* [HGM] can be >= 10 */
16334                         *p++ = '0' + emptycount;
16335                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16336                     emptycount = 0;
16337                 }
16338                 if(PieceToChar(piece) == '+') {
16339                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16340                     *p++ = '+';
16341                     piece = (ChessSquare)(DEMOTED piece);
16342                 }
16343                 *p++ = PieceToChar(piece);
16344                 if(p[-1] == '~') {
16345                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16346                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16347                     *p++ = '~';
16348                 }
16349             }
16350         }
16351         if (emptycount > 0) {
16352             if(emptycount<10) /* [HGM] can be >= 10 */
16353                 *p++ = '0' + emptycount;
16354             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16355             emptycount = 0;
16356         }
16357         *p++ = '/';
16358     }
16359     *(p - 1) = ' ';
16360
16361     /* [HGM] print Crazyhouse or Shogi holdings */
16362     if( gameInfo.holdingsWidth ) {
16363         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16364         q = p;
16365         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16366             piece = boards[move][i][BOARD_WIDTH-1];
16367             if( piece != EmptySquare )
16368               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16369                   *p++ = PieceToChar(piece);
16370         }
16371         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16372             piece = boards[move][BOARD_HEIGHT-i-1][0];
16373             if( piece != EmptySquare )
16374               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16375                   *p++ = PieceToChar(piece);
16376         }
16377
16378         if( q == p ) *p++ = '-';
16379         *p++ = ']';
16380         *p++ = ' ';
16381     }
16382
16383     /* Active color */
16384     *p++ = whiteToPlay ? 'w' : 'b';
16385     *p++ = ' ';
16386
16387   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16388     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16389   } else {
16390   if(nrCastlingRights) {
16391      q = p;
16392      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16393        /* [HGM] write directly from rights */
16394            if(boards[move][CASTLING][2] != NoRights &&
16395               boards[move][CASTLING][0] != NoRights   )
16396                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16397            if(boards[move][CASTLING][2] != NoRights &&
16398               boards[move][CASTLING][1] != NoRights   )
16399                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16400            if(boards[move][CASTLING][5] != NoRights &&
16401               boards[move][CASTLING][3] != NoRights   )
16402                 *p++ = boards[move][CASTLING][3] + AAA;
16403            if(boards[move][CASTLING][5] != NoRights &&
16404               boards[move][CASTLING][4] != NoRights   )
16405                 *p++ = boards[move][CASTLING][4] + AAA;
16406      } else {
16407
16408         /* [HGM] write true castling rights */
16409         if( nrCastlingRights == 6 ) {
16410             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16411                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16412             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16413                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16414             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16415                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16416             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16417                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16418         }
16419      }
16420      if (q == p) *p++ = '-'; /* No castling rights */
16421      *p++ = ' ';
16422   }
16423
16424   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16425      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16426     /* En passant target square */
16427     if (move > backwardMostMove) {
16428         fromX = moveList[move - 1][0] - AAA;
16429         fromY = moveList[move - 1][1] - ONE;
16430         toX = moveList[move - 1][2] - AAA;
16431         toY = moveList[move - 1][3] - ONE;
16432         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16433             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16434             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16435             fromX == toX) {
16436             /* 2-square pawn move just happened */
16437             *p++ = toX + AAA;
16438             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16439         } else {
16440             *p++ = '-';
16441         }
16442     } else if(move == backwardMostMove) {
16443         // [HGM] perhaps we should always do it like this, and forget the above?
16444         if((signed char)boards[move][EP_STATUS] >= 0) {
16445             *p++ = boards[move][EP_STATUS] + AAA;
16446             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16447         } else {
16448             *p++ = '-';
16449         }
16450     } else {
16451         *p++ = '-';
16452     }
16453     *p++ = ' ';
16454   }
16455   }
16456
16457     /* [HGM] find reversible plies */
16458     {   int i = 0, j=move;
16459
16460         if (appData.debugMode) { int k;
16461             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16462             for(k=backwardMostMove; k<=forwardMostMove; k++)
16463                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16464
16465         }
16466
16467         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16468         if( j == backwardMostMove ) i += initialRulePlies;
16469         sprintf(p, "%d ", i);
16470         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16471     }
16472     /* Fullmove number */
16473     sprintf(p, "%d", (move / 2) + 1);
16474
16475     return StrSave(buf);
16476 }
16477
16478 Boolean
16479 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16480 {
16481     int i, j;
16482     char *p, c;
16483     int emptycount;
16484     ChessSquare piece;
16485
16486     p = fen;
16487
16488     /* [HGM] by default clear Crazyhouse holdings, if present */
16489     if(gameInfo.holdingsWidth) {
16490        for(i=0; i<BOARD_HEIGHT; i++) {
16491            board[i][0]             = EmptySquare; /* black holdings */
16492            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16493            board[i][1]             = (ChessSquare) 0; /* black counts */
16494            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16495        }
16496     }
16497
16498     /* Piece placement data */
16499     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16500         j = 0;
16501         for (;;) {
16502             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16503                 if (*p == '/') p++;
16504                 emptycount = gameInfo.boardWidth - j;
16505                 while (emptycount--)
16506                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16507                 break;
16508 #if(BOARD_FILES >= 10)
16509             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16510                 p++; emptycount=10;
16511                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16512                 while (emptycount--)
16513                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16514 #endif
16515             } else if (isdigit(*p)) {
16516                 emptycount = *p++ - '0';
16517                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16518                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16519                 while (emptycount--)
16520                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16521             } else if (*p == '+' || isalpha(*p)) {
16522                 if (j >= gameInfo.boardWidth) return FALSE;
16523                 if(*p=='+') {
16524                     piece = CharToPiece(*++p);
16525                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16526                     piece = (ChessSquare) (PROMOTED piece ); p++;
16527                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16528                 } else piece = CharToPiece(*p++);
16529
16530                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16531                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16532                     piece = (ChessSquare) (PROMOTED piece);
16533                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16534                     p++;
16535                 }
16536                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16537             } else {
16538                 return FALSE;
16539             }
16540         }
16541     }
16542     while (*p == '/' || *p == ' ') p++;
16543
16544     /* [HGM] look for Crazyhouse holdings here */
16545     while(*p==' ') p++;
16546     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16547         if(*p == '[') p++;
16548         if(*p == '-' ) p++; /* empty holdings */ else {
16549             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16550             /* if we would allow FEN reading to set board size, we would   */
16551             /* have to add holdings and shift the board read so far here   */
16552             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16553                 p++;
16554                 if((int) piece >= (int) BlackPawn ) {
16555                     i = (int)piece - (int)BlackPawn;
16556                     i = PieceToNumber((ChessSquare)i);
16557                     if( i >= gameInfo.holdingsSize ) return FALSE;
16558                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16559                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16560                 } else {
16561                     i = (int)piece - (int)WhitePawn;
16562                     i = PieceToNumber((ChessSquare)i);
16563                     if( i >= gameInfo.holdingsSize ) return FALSE;
16564                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16565                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16566                 }
16567             }
16568         }
16569         if(*p == ']') p++;
16570     }
16571
16572     while(*p == ' ') p++;
16573
16574     /* Active color */
16575     c = *p++;
16576     if(appData.colorNickNames) {
16577       if( c == appData.colorNickNames[0] ) c = 'w'; else
16578       if( c == appData.colorNickNames[1] ) c = 'b';
16579     }
16580     switch (c) {
16581       case 'w':
16582         *blackPlaysFirst = FALSE;
16583         break;
16584       case 'b':
16585         *blackPlaysFirst = TRUE;
16586         break;
16587       default:
16588         return FALSE;
16589     }
16590
16591     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16592     /* return the extra info in global variiables             */
16593
16594     /* set defaults in case FEN is incomplete */
16595     board[EP_STATUS] = EP_UNKNOWN;
16596     for(i=0; i<nrCastlingRights; i++ ) {
16597         board[CASTLING][i] =
16598             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16599     }   /* assume possible unless obviously impossible */
16600     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16601     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16602     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16603                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16604     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16605     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16606     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16607                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16608     FENrulePlies = 0;
16609
16610     while(*p==' ') p++;
16611     if(nrCastlingRights) {
16612       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16613           /* castling indicator present, so default becomes no castlings */
16614           for(i=0; i<nrCastlingRights; i++ ) {
16615                  board[CASTLING][i] = NoRights;
16616           }
16617       }
16618       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16619              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16620              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16621              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16622         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16623
16624         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16625             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16626             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16627         }
16628         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16629             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16630         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16631                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16632         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16633                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16634         switch(c) {
16635           case'K':
16636               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16637               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16638               board[CASTLING][2] = whiteKingFile;
16639               break;
16640           case'Q':
16641               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16642               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16643               board[CASTLING][2] = whiteKingFile;
16644               break;
16645           case'k':
16646               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16647               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16648               board[CASTLING][5] = blackKingFile;
16649               break;
16650           case'q':
16651               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16652               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16653               board[CASTLING][5] = blackKingFile;
16654           case '-':
16655               break;
16656           default: /* FRC castlings */
16657               if(c >= 'a') { /* black rights */
16658                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16659                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16660                   if(i == BOARD_RGHT) break;
16661                   board[CASTLING][5] = i;
16662                   c -= AAA;
16663                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16664                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16665                   if(c > i)
16666                       board[CASTLING][3] = c;
16667                   else
16668                       board[CASTLING][4] = c;
16669               } else { /* white rights */
16670                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16671                     if(board[0][i] == WhiteKing) break;
16672                   if(i == BOARD_RGHT) break;
16673                   board[CASTLING][2] = i;
16674                   c -= AAA - 'a' + 'A';
16675                   if(board[0][c] >= WhiteKing) break;
16676                   if(c > i)
16677                       board[CASTLING][0] = c;
16678                   else
16679                       board[CASTLING][1] = c;
16680               }
16681         }
16682       }
16683       for(i=0; i<nrCastlingRights; i++)
16684         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16685     if (appData.debugMode) {
16686         fprintf(debugFP, "FEN castling rights:");
16687         for(i=0; i<nrCastlingRights; i++)
16688         fprintf(debugFP, " %d", board[CASTLING][i]);
16689         fprintf(debugFP, "\n");
16690     }
16691
16692       while(*p==' ') p++;
16693     }
16694
16695     /* read e.p. field in games that know e.p. capture */
16696     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16697        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16698       if(*p=='-') {
16699         p++; board[EP_STATUS] = EP_NONE;
16700       } else {
16701          char c = *p++ - AAA;
16702
16703          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16704          if(*p >= '0' && *p <='9') p++;
16705          board[EP_STATUS] = c;
16706       }
16707     }
16708
16709
16710     if(sscanf(p, "%d", &i) == 1) {
16711         FENrulePlies = i; /* 50-move ply counter */
16712         /* (The move number is still ignored)    */
16713     }
16714
16715     return TRUE;
16716 }
16717
16718 void
16719 EditPositionPasteFEN (char *fen)
16720 {
16721   if (fen != NULL) {
16722     Board initial_position;
16723
16724     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16725       DisplayError(_("Bad FEN position in clipboard"), 0);
16726       return ;
16727     } else {
16728       int savedBlackPlaysFirst = blackPlaysFirst;
16729       EditPositionEvent();
16730       blackPlaysFirst = savedBlackPlaysFirst;
16731       CopyBoard(boards[0], initial_position);
16732       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16733       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16734       DisplayBothClocks();
16735       DrawPosition(FALSE, boards[currentMove]);
16736     }
16737   }
16738 }
16739
16740 static char cseq[12] = "\\   ";
16741
16742 Boolean
16743 set_cont_sequence (char *new_seq)
16744 {
16745     int len;
16746     Boolean ret;
16747
16748     // handle bad attempts to set the sequence
16749         if (!new_seq)
16750                 return 0; // acceptable error - no debug
16751
16752     len = strlen(new_seq);
16753     ret = (len > 0) && (len < sizeof(cseq));
16754     if (ret)
16755       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16756     else if (appData.debugMode)
16757       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16758     return ret;
16759 }
16760
16761 /*
16762     reformat a source message so words don't cross the width boundary.  internal
16763     newlines are not removed.  returns the wrapped size (no null character unless
16764     included in source message).  If dest is NULL, only calculate the size required
16765     for the dest buffer.  lp argument indicats line position upon entry, and it's
16766     passed back upon exit.
16767 */
16768 int
16769 wrap (char *dest, char *src, int count, int width, int *lp)
16770 {
16771     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16772
16773     cseq_len = strlen(cseq);
16774     old_line = line = *lp;
16775     ansi = len = clen = 0;
16776
16777     for (i=0; i < count; i++)
16778     {
16779         if (src[i] == '\033')
16780             ansi = 1;
16781
16782         // if we hit the width, back up
16783         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16784         {
16785             // store i & len in case the word is too long
16786             old_i = i, old_len = len;
16787
16788             // find the end of the last word
16789             while (i && src[i] != ' ' && src[i] != '\n')
16790             {
16791                 i--;
16792                 len--;
16793             }
16794
16795             // word too long?  restore i & len before splitting it
16796             if ((old_i-i+clen) >= width)
16797             {
16798                 i = old_i;
16799                 len = old_len;
16800             }
16801
16802             // extra space?
16803             if (i && src[i-1] == ' ')
16804                 len--;
16805
16806             if (src[i] != ' ' && src[i] != '\n')
16807             {
16808                 i--;
16809                 if (len)
16810                     len--;
16811             }
16812
16813             // now append the newline and continuation sequence
16814             if (dest)
16815                 dest[len] = '\n';
16816             len++;
16817             if (dest)
16818                 strncpy(dest+len, cseq, cseq_len);
16819             len += cseq_len;
16820             line = cseq_len;
16821             clen = cseq_len;
16822             continue;
16823         }
16824
16825         if (dest)
16826             dest[len] = src[i];
16827         len++;
16828         if (!ansi)
16829             line++;
16830         if (src[i] == '\n')
16831             line = 0;
16832         if (src[i] == 'm')
16833             ansi = 0;
16834     }
16835     if (dest && appData.debugMode)
16836     {
16837         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16838             count, width, line, len, *lp);
16839         show_bytes(debugFP, src, count);
16840         fprintf(debugFP, "\ndest: ");
16841         show_bytes(debugFP, dest, len);
16842         fprintf(debugFP, "\n");
16843     }
16844     *lp = dest ? line : old_line;
16845
16846     return len;
16847 }
16848
16849 // [HGM] vari: routines for shelving variations
16850 Boolean modeRestore = FALSE;
16851
16852 void
16853 PushInner (int firstMove, int lastMove)
16854 {
16855         int i, j, nrMoves = lastMove - firstMove;
16856
16857         // push current tail of game on stack
16858         savedResult[storedGames] = gameInfo.result;
16859         savedDetails[storedGames] = gameInfo.resultDetails;
16860         gameInfo.resultDetails = NULL;
16861         savedFirst[storedGames] = firstMove;
16862         savedLast [storedGames] = lastMove;
16863         savedFramePtr[storedGames] = framePtr;
16864         framePtr -= nrMoves; // reserve space for the boards
16865         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16866             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16867             for(j=0; j<MOVE_LEN; j++)
16868                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16869             for(j=0; j<2*MOVE_LEN; j++)
16870                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16871             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16872             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16873             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16874             pvInfoList[firstMove+i-1].depth = 0;
16875             commentList[framePtr+i] = commentList[firstMove+i];
16876             commentList[firstMove+i] = NULL;
16877         }
16878
16879         storedGames++;
16880         forwardMostMove = firstMove; // truncate game so we can start variation
16881 }
16882
16883 void
16884 PushTail (int firstMove, int lastMove)
16885 {
16886         if(appData.icsActive) { // only in local mode
16887                 forwardMostMove = currentMove; // mimic old ICS behavior
16888                 return;
16889         }
16890         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16891
16892         PushInner(firstMove, lastMove);
16893         if(storedGames == 1) GreyRevert(FALSE);
16894         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16895 }
16896
16897 void
16898 PopInner (Boolean annotate)
16899 {
16900         int i, j, nrMoves;
16901         char buf[8000], moveBuf[20];
16902
16903         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16904         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16905         nrMoves = savedLast[storedGames] - currentMove;
16906         if(annotate) {
16907                 int cnt = 10;
16908                 if(!WhiteOnMove(currentMove))
16909                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16910                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16911                 for(i=currentMove; i<forwardMostMove; i++) {
16912                         if(WhiteOnMove(i))
16913                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16914                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16915                         strcat(buf, moveBuf);
16916                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16917                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16918                 }
16919                 strcat(buf, ")");
16920         }
16921         for(i=1; i<=nrMoves; i++) { // copy last variation back
16922             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16923             for(j=0; j<MOVE_LEN; j++)
16924                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16925             for(j=0; j<2*MOVE_LEN; j++)
16926                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16927             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16928             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16929             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16930             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16931             commentList[currentMove+i] = commentList[framePtr+i];
16932             commentList[framePtr+i] = NULL;
16933         }
16934         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16935         framePtr = savedFramePtr[storedGames];
16936         gameInfo.result = savedResult[storedGames];
16937         if(gameInfo.resultDetails != NULL) {
16938             free(gameInfo.resultDetails);
16939       }
16940         gameInfo.resultDetails = savedDetails[storedGames];
16941         forwardMostMove = currentMove + nrMoves;
16942 }
16943
16944 Boolean
16945 PopTail (Boolean annotate)
16946 {
16947         if(appData.icsActive) return FALSE; // only in local mode
16948         if(!storedGames) return FALSE; // sanity
16949         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16950
16951         PopInner(annotate);
16952         if(currentMove < forwardMostMove) ForwardEvent(); else
16953         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16954
16955         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16956         return TRUE;
16957 }
16958
16959 void
16960 CleanupTail ()
16961 {       // remove all shelved variations
16962         int i;
16963         for(i=0; i<storedGames; i++) {
16964             if(savedDetails[i])
16965                 free(savedDetails[i]);
16966             savedDetails[i] = NULL;
16967         }
16968         for(i=framePtr; i<MAX_MOVES; i++) {
16969                 if(commentList[i]) free(commentList[i]);
16970                 commentList[i] = NULL;
16971         }
16972         framePtr = MAX_MOVES-1;
16973         storedGames = 0;
16974 }
16975
16976 void
16977 LoadVariation (int index, char *text)
16978 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16979         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16980         int level = 0, move;
16981
16982         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16983         // first find outermost bracketing variation
16984         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16985             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16986                 if(*p == '{') wait = '}'; else
16987                 if(*p == '[') wait = ']'; else
16988                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16989                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16990             }
16991             if(*p == wait) wait = NULLCHAR; // closing ]} found
16992             p++;
16993         }
16994         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16995         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16996         end[1] = NULLCHAR; // clip off comment beyond variation
16997         ToNrEvent(currentMove-1);
16998         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16999         // kludge: use ParsePV() to append variation to game
17000         move = currentMove;
17001         ParsePV(start, TRUE, TRUE);
17002         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17003         ClearPremoveHighlights();
17004         CommentPopDown();
17005         ToNrEvent(currentMove+1);
17006 }
17007