e062eff2adb9b28d4938dc92def6291f4b7b06ed
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "gettext.h"
133
134 #ifdef ENABLE_NLS
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
138 #else
139 # ifdef WIN32
140 #   define _(s) T_(s)
141 #   define N_(s) s
142 # else
143 #   define _(s) (s)
144 #   define N_(s) s
145 #   define T_(s) s
146 # endif
147 #endif
148
149
150 int establish P((void));
151 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
152                          char *buf, int count, int error));
153 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
154                       char *buf, int count, int error));
155 void ics_printf P((char *format, ...));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225
226 #ifdef WIN32
227        extern void ConsoleCreate();
228 #endif
229
230 ChessProgramState *WhitePlayer();
231 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
232 int VerifyDisplayMode P(());
233
234 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
235 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
236 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
237 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
238 void ics_update_width P((int new_width));
239 extern char installDir[MSG_SIZ];
240 VariantClass startVariant; /* [HGM] nicks: initial variant */
241 Boolean abortMatch;
242
243 extern int tinyLayout, smallLayout;
244 ChessProgramStats programStats;
245 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
246 int endPV = -1;
247 static int exiting = 0; /* [HGM] moved to top */
248 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
249 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
250 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
251 int partnerHighlight[2];
252 Boolean partnerBoardValid = 0;
253 char partnerStatus[MSG_SIZ];
254 Boolean partnerUp;
255 Boolean originalFlip;
256 Boolean twoBoards = 0;
257 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
258 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
259 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
260 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
261 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
262 int opponentKibitzes;
263 int lastSavedGame; /* [HGM] save: ID of game */
264 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
265 extern int chatCount;
266 int chattingPartner;
267 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
268 char lastMsg[MSG_SIZ];
269 ChessSquare pieceSweep = EmptySquare;
270 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
271 int promoDefaultAltered;
272
273 /* States for ics_getting_history */
274 #define H_FALSE 0
275 #define H_REQUESTED 1
276 #define H_GOT_REQ_HEADER 2
277 #define H_GOT_UNREQ_HEADER 3
278 #define H_GETTING_MOVES 4
279 #define H_GOT_UNWANTED_HEADER 5
280
281 /* whosays values for GameEnds */
282 #define GE_ICS 0
283 #define GE_ENGINE 1
284 #define GE_PLAYER 2
285 #define GE_FILE 3
286 #define GE_XBOARD 4
287 #define GE_ENGINE1 5
288 #define GE_ENGINE2 6
289
290 /* Maximum number of games in a cmail message */
291 #define CMAIL_MAX_GAMES 20
292
293 /* Different types of move when calling RegisterMove */
294 #define CMAIL_MOVE   0
295 #define CMAIL_RESIGN 1
296 #define CMAIL_DRAW   2
297 #define CMAIL_ACCEPT 3
298
299 /* Different types of result to remember for each game */
300 #define CMAIL_NOT_RESULT 0
301 #define CMAIL_OLD_RESULT 1
302 #define CMAIL_NEW_RESULT 2
303
304 /* Telnet protocol constants */
305 #define TN_WILL 0373
306 #define TN_WONT 0374
307 #define TN_DO   0375
308 #define TN_DONT 0376
309 #define TN_IAC  0377
310 #define TN_ECHO 0001
311 #define TN_SGA  0003
312 #define TN_PORT 23
313
314 char*
315 safeStrCpy (char *dst, const char *src, size_t count)
316 { // [HGM] made safe
317   int i;
318   assert( dst != NULL );
319   assert( src != NULL );
320   assert( count > 0 );
321
322   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
323   if(  i == count && dst[count-1] != NULLCHAR)
324     {
325       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
326       if(appData.debugMode)
327       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
328     }
329
330   return dst;
331 }
332
333 /* Some compiler can't cast u64 to double
334  * This function do the job for us:
335
336  * We use the highest bit for cast, this only
337  * works if the highest bit is not
338  * in use (This should not happen)
339  *
340  * We used this for all compiler
341  */
342 double
343 u64ToDouble (u64 value)
344 {
345   double r;
346   u64 tmp = value & u64Const(0x7fffffffffffffff);
347   r = (double)(s64)tmp;
348   if (value & u64Const(0x8000000000000000))
349        r +=  9.2233720368547758080e18; /* 2^63 */
350  return r;
351 }
352
353 /* Fake up flags for now, as we aren't keeping track of castling
354    availability yet. [HGM] Change of logic: the flag now only
355    indicates the type of castlings allowed by the rule of the game.
356    The actual rights themselves are maintained in the array
357    castlingRights, as part of the game history, and are not probed
358    by this function.
359  */
360 int
361 PosFlags (index)
362 {
363   int flags = F_ALL_CASTLE_OK;
364   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
365   switch (gameInfo.variant) {
366   case VariantSuicide:
367     flags &= ~F_ALL_CASTLE_OK;
368   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
369     flags |= F_IGNORE_CHECK;
370   case VariantLosers:
371     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
372     break;
373   case VariantAtomic:
374     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
375     break;
376   case VariantKriegspiel:
377     flags |= F_KRIEGSPIEL_CAPTURE;
378     break;
379   case VariantCapaRandom:
380   case VariantFischeRandom:
381     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
382   case VariantNoCastle:
383   case VariantShatranj:
384   case VariantCourier:
385   case VariantMakruk:
386   case VariantGrand:
387     flags &= ~F_ALL_CASTLE_OK;
388     break;
389   default:
390     break;
391   }
392   return flags;
393 }
394
395 FILE *gameFileFP, *debugFP;
396
397 /*
398     [AS] Note: sometimes, the sscanf() function is used to parse the input
399     into a fixed-size buffer. Because of this, we must be prepared to
400     receive strings as long as the size of the input buffer, which is currently
401     set to 4K for Windows and 8K for the rest.
402     So, we must either allocate sufficiently large buffers here, or
403     reduce the size of the input buffer in the input reading part.
404 */
405
406 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
407 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
408 char thinkOutput1[MSG_SIZ*10];
409
410 ChessProgramState first, second, pairing;
411
412 /* premove variables */
413 int premoveToX = 0;
414 int premoveToY = 0;
415 int premoveFromX = 0;
416 int premoveFromY = 0;
417 int premovePromoChar = 0;
418 int gotPremove = 0;
419 Boolean alarmSounded;
420 /* end premove variables */
421
422 char *ics_prefix = "$";
423 int ics_type = ICS_GENERIC;
424
425 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
426 int pauseExamForwardMostMove = 0;
427 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
428 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
429 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
430 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
431 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
432 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
433 int whiteFlag = FALSE, blackFlag = FALSE;
434 int userOfferedDraw = FALSE;
435 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
436 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
437 int cmailMoveType[CMAIL_MAX_GAMES];
438 long ics_clock_paused = 0;
439 ProcRef icsPR = NoProc, cmailPR = NoProc;
440 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
441 GameMode gameMode = BeginningOfGame;
442 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
443 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
444 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
445 int hiddenThinkOutputState = 0; /* [AS] */
446 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
447 int adjudicateLossPlies = 6;
448 char white_holding[64], black_holding[64];
449 TimeMark lastNodeCountTime;
450 long lastNodeCount=0;
451 int shiftKey; // [HGM] set by mouse handler
452
453 int have_sent_ICS_logon = 0;
454 int movesPerSession;
455 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
456 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
457 Boolean adjustedClock;
458 long timeControl_2; /* [AS] Allow separate time controls */
459 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
460 long timeRemaining[2][MAX_MOVES];
461 int matchGame = 0, nextGame = 0, roundNr = 0;
462 Boolean waitingForGame = FALSE;
463 TimeMark programStartTime, pauseStart;
464 char ics_handle[MSG_SIZ];
465 int have_set_title = 0;
466
467 /* animateTraining preserves the state of appData.animate
468  * when Training mode is activated. This allows the
469  * response to be animated when appData.animate == TRUE and
470  * appData.animateDragging == TRUE.
471  */
472 Boolean animateTraining;
473
474 GameInfo gameInfo;
475
476 AppData appData;
477
478 Board boards[MAX_MOVES];
479 /* [HGM] Following 7 needed for accurate legality tests: */
480 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
481 signed char  initialRights[BOARD_FILES];
482 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
483 int   initialRulePlies, FENrulePlies;
484 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
485 int loadFlag = 0;
486 Boolean shuffleOpenings;
487 int mute; // mute all sounds
488
489 // [HGM] vari: next 12 to save and restore variations
490 #define MAX_VARIATIONS 10
491 int framePtr = MAX_MOVES-1; // points to free stack entry
492 int storedGames = 0;
493 int savedFirst[MAX_VARIATIONS];
494 int savedLast[MAX_VARIATIONS];
495 int savedFramePtr[MAX_VARIATIONS];
496 char *savedDetails[MAX_VARIATIONS];
497 ChessMove savedResult[MAX_VARIATIONS];
498
499 void PushTail P((int firstMove, int lastMove));
500 Boolean PopTail P((Boolean annotate));
501 void PushInner P((int firstMove, int lastMove));
502 void PopInner P((Boolean annotate));
503 void CleanupTail P((void));
504
505 ChessSquare  FIDEArray[2][BOARD_FILES] = {
506     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
507         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
508     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
509         BlackKing, BlackBishop, BlackKnight, BlackRook }
510 };
511
512 ChessSquare twoKingsArray[2][BOARD_FILES] = {
513     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
514         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
515     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
516         BlackKing, BlackKing, BlackKnight, BlackRook }
517 };
518
519 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
520     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
521         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
522     { BlackRook, BlackMan, BlackBishop, BlackQueen,
523         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
524 };
525
526 ChessSquare SpartanArray[2][BOARD_FILES] = {
527     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
528         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
529     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
530         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
531 };
532
533 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
534     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
535         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
536     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
537         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
538 };
539
540 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
541     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
542         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
544         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
545 };
546
547 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
548     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
549         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
550     { BlackRook, BlackKnight, BlackMan, BlackFerz,
551         BlackKing, BlackMan, BlackKnight, BlackRook }
552 };
553
554
555 #if (BOARD_FILES>=10)
556 ChessSquare ShogiArray[2][BOARD_FILES] = {
557     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
558         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
559     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
560         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
561 };
562
563 ChessSquare XiangqiArray[2][BOARD_FILES] = {
564     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
565         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
566     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
567         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
568 };
569
570 ChessSquare CapablancaArray[2][BOARD_FILES] = {
571     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
572         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
573     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
574         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
575 };
576
577 ChessSquare GreatArray[2][BOARD_FILES] = {
578     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
579         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
580     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
581         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
582 };
583
584 ChessSquare JanusArray[2][BOARD_FILES] = {
585     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
586         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
587     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
588         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
589 };
590
591 ChessSquare GrandArray[2][BOARD_FILES] = {
592     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
593         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
594     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
595         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
596 };
597
598 #ifdef GOTHIC
599 ChessSquare GothicArray[2][BOARD_FILES] = {
600     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
601         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
602     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
603         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
604 };
605 #else // !GOTHIC
606 #define GothicArray CapablancaArray
607 #endif // !GOTHIC
608
609 #ifdef FALCON
610 ChessSquare FalconArray[2][BOARD_FILES] = {
611     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
612         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
613     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
614         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
615 };
616 #else // !FALCON
617 #define FalconArray CapablancaArray
618 #endif // !FALCON
619
620 #else // !(BOARD_FILES>=10)
621 #define XiangqiPosition FIDEArray
622 #define CapablancaArray FIDEArray
623 #define GothicArray FIDEArray
624 #define GreatArray FIDEArray
625 #endif // !(BOARD_FILES>=10)
626
627 #if (BOARD_FILES>=12)
628 ChessSquare CourierArray[2][BOARD_FILES] = {
629     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
630         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
631     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
632         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
633 };
634 #else // !(BOARD_FILES>=12)
635 #define CourierArray CapablancaArray
636 #endif // !(BOARD_FILES>=12)
637
638
639 Board initialPosition;
640
641
642 /* Convert str to a rating. Checks for special cases of "----",
643
644    "++++", etc. Also strips ()'s */
645 int
646 string_to_rating (char *str)
647 {
648   while(*str && !isdigit(*str)) ++str;
649   if (!*str)
650     return 0;   /* One of the special "no rating" cases */
651   else
652     return atoi(str);
653 }
654
655 void
656 ClearProgramStats ()
657 {
658     /* Init programStats */
659     programStats.movelist[0] = 0;
660     programStats.depth = 0;
661     programStats.nr_moves = 0;
662     programStats.moves_left = 0;
663     programStats.nodes = 0;
664     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
665     programStats.score = 0;
666     programStats.got_only_move = 0;
667     programStats.got_fail = 0;
668     programStats.line_is_book = 0;
669 }
670
671 void
672 CommonEngineInit ()
673 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
674     if (appData.firstPlaysBlack) {
675         first.twoMachinesColor = "black\n";
676         second.twoMachinesColor = "white\n";
677     } else {
678         first.twoMachinesColor = "white\n";
679         second.twoMachinesColor = "black\n";
680     }
681
682     first.other = &second;
683     second.other = &first;
684
685     { float norm = 1;
686         if(appData.timeOddsMode) {
687             norm = appData.timeOdds[0];
688             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
689         }
690         first.timeOdds  = appData.timeOdds[0]/norm;
691         second.timeOdds = appData.timeOdds[1]/norm;
692     }
693
694     if(programVersion) free(programVersion);
695     if (appData.noChessProgram) {
696         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
697         sprintf(programVersion, "%s", PACKAGE_STRING);
698     } else {
699       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
700       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
701       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
702     }
703 }
704
705 void
706 UnloadEngine (ChessProgramState *cps)
707 {
708         /* Kill off first chess program */
709         if (cps->isr != NULL)
710           RemoveInputSource(cps->isr);
711         cps->isr = NULL;
712
713         if (cps->pr != NoProc) {
714             ExitAnalyzeMode();
715             DoSleep( appData.delayBeforeQuit );
716             SendToProgram("quit\n", cps);
717             DoSleep( appData.delayAfterQuit );
718             DestroyChildProcess(cps->pr, cps->useSigterm);
719         }
720         cps->pr = NoProc;
721         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
722 }
723
724 void
725 ClearOptions (ChessProgramState *cps)
726 {
727     int i;
728     cps->nrOptions = cps->comboCnt = 0;
729     for(i=0; i<MAX_OPTIONS; i++) {
730         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
731         cps->option[i].textValue = 0;
732     }
733 }
734
735 char *engineNames[] = {
736 "first",
737 "second"
738 };
739
740 void
741 InitEngine (ChessProgramState *cps, int n)
742 {   // [HGM] all engine initialiation put in a function that does one engine
743
744     ClearOptions(cps);
745
746     cps->which = engineNames[n];
747     cps->maybeThinking = FALSE;
748     cps->pr = NoProc;
749     cps->isr = NULL;
750     cps->sendTime = 2;
751     cps->sendDrawOffers = 1;
752
753     cps->program = appData.chessProgram[n];
754     cps->host = appData.host[n];
755     cps->dir = appData.directory[n];
756     cps->initString = appData.engInitString[n];
757     cps->computerString = appData.computerString[n];
758     cps->useSigint  = TRUE;
759     cps->useSigterm = TRUE;
760     cps->reuse = appData.reuse[n];
761     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
762     cps->useSetboard = FALSE;
763     cps->useSAN = FALSE;
764     cps->usePing = FALSE;
765     cps->lastPing = 0;
766     cps->lastPong = 0;
767     cps->usePlayother = FALSE;
768     cps->useColors = TRUE;
769     cps->useUsermove = FALSE;
770     cps->sendICS = FALSE;
771     cps->sendName = appData.icsActive;
772     cps->sdKludge = FALSE;
773     cps->stKludge = FALSE;
774     TidyProgramName(cps->program, cps->host, cps->tidy);
775     cps->matchWins = 0;
776     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
777     cps->analysisSupport = 2; /* detect */
778     cps->analyzing = FALSE;
779     cps->initDone = FALSE;
780
781     /* New features added by Tord: */
782     cps->useFEN960 = FALSE;
783     cps->useOOCastle = TRUE;
784     /* End of new features added by Tord. */
785     cps->fenOverride  = appData.fenOverride[n];
786
787     /* [HGM] time odds: set factor for each machine */
788     cps->timeOdds  = appData.timeOdds[n];
789
790     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
791     cps->accumulateTC = appData.accumulateTC[n];
792     cps->maxNrOfSessions = 1;
793
794     /* [HGM] debug */
795     cps->debug = FALSE;
796
797     cps->supportsNPS = UNKNOWN;
798     cps->memSize = FALSE;
799     cps->maxCores = FALSE;
800     cps->egtFormats[0] = NULLCHAR;
801
802     /* [HGM] options */
803     cps->optionSettings  = appData.engOptions[n];
804
805     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
806     cps->isUCI = appData.isUCI[n]; /* [AS] */
807     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
808
809     if (appData.protocolVersion[n] > PROTOVER
810         || appData.protocolVersion[n] < 1)
811       {
812         char buf[MSG_SIZ];
813         int len;
814
815         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
816                        appData.protocolVersion[n]);
817         if( (len >= MSG_SIZ) && appData.debugMode )
818           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
819
820         DisplayFatalError(buf, 0, 2);
821       }
822     else
823       {
824         cps->protocolVersion = appData.protocolVersion[n];
825       }
826
827     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
828     ParseFeatures(appData.featureDefaults, cps);
829 }
830
831 ChessProgramState *savCps;
832
833 void
834 LoadEngine ()
835 {
836     int i;
837     if(WaitForEngine(savCps, LoadEngine)) return;
838     CommonEngineInit(); // recalculate time odds
839     if(gameInfo.variant != StringToVariant(appData.variant)) {
840         // we changed variant when loading the engine; this forces us to reset
841         Reset(TRUE, savCps != &first);
842         EditGameEvent(); // for consistency with other path, as Reset changes mode
843     }
844     InitChessProgram(savCps, FALSE);
845     SendToProgram("force\n", savCps);
846     DisplayMessage("", "");
847     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
848     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
849     ThawUI();
850     SetGNUMode();
851 }
852
853 void
854 ReplaceEngine (ChessProgramState *cps, int n)
855 {
856     EditGameEvent();
857     UnloadEngine(cps);
858     appData.noChessProgram = FALSE;
859     appData.clockMode = TRUE;
860     InitEngine(cps, n);
861     UpdateLogos(TRUE);
862     if(n) return; // only startup first engine immediately; second can wait
863     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
864     LoadEngine();
865 }
866
867 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
868 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
869
870 static char resetOptions[] = 
871         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
872         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
873         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
874
875 void
876 Load (ChessProgramState *cps, int i)
877 {
878     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
879     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
880         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
881         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
882         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
883         ParseArgsFromString(buf);
884         SwapEngines(i);
885         ReplaceEngine(cps, i);
886         return;
887     }
888     p = engineName;
889     while(q = strchr(p, SLASH)) p = q+1;
890     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
891     if(engineDir[0] != NULLCHAR)
892         appData.directory[i] = engineDir;
893     else if(p != engineName) { // derive directory from engine path, when not given
894         p[-1] = 0;
895         appData.directory[i] = strdup(engineName);
896         p[-1] = SLASH;
897     } else appData.directory[i] = ".";
898     if(params[0]) {
899         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
900         snprintf(command, MSG_SIZ, "%s %s", p, params);
901         p = command;
902     }
903     appData.chessProgram[i] = strdup(p);
904     appData.isUCI[i] = isUCI;
905     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
906     appData.hasOwnBookUCI[i] = hasBook;
907     if(!nickName[0]) useNick = FALSE;
908     if(useNick) ASSIGN(appData.pgnName[i], nickName);
909     if(addToList) {
910         int len;
911         char quote;
912         q = firstChessProgramNames;
913         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
914         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
915         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
916                         quote, p, quote, appData.directory[i], 
917                         useNick ? " -fn \"" : "",
918                         useNick ? nickName : "",
919                         useNick ? "\"" : "",
920                         v1 ? " -firstProtocolVersion 1" : "",
921                         hasBook ? "" : " -fNoOwnBookUCI",
922                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
923                         storeVariant ? " -variant " : "",
924                         storeVariant ? VariantName(gameInfo.variant) : "");
925         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
926         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
927         if(q)   free(q);
928     }
929     ReplaceEngine(cps, i);
930 }
931
932 void
933 InitTimeControls ()
934 {
935     int matched, min, sec;
936     /*
937      * Parse timeControl resource
938      */
939     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
940                           appData.movesPerSession)) {
941         char buf[MSG_SIZ];
942         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
943         DisplayFatalError(buf, 0, 2);
944     }
945
946     /*
947      * Parse searchTime resource
948      */
949     if (*appData.searchTime != NULLCHAR) {
950         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
951         if (matched == 1) {
952             searchTime = min * 60;
953         } else if (matched == 2) {
954             searchTime = min * 60 + sec;
955         } else {
956             char buf[MSG_SIZ];
957             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
958             DisplayFatalError(buf, 0, 2);
959         }
960     }
961 }
962
963 void
964 InitBackEnd1 ()
965 {
966
967     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
968     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
969
970     GetTimeMark(&programStartTime);
971     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
972     appData.seedBase = random() + (random()<<15);
973     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
974
975     ClearProgramStats();
976     programStats.ok_to_send = 1;
977     programStats.seen_stat = 0;
978
979     /*
980      * Initialize game list
981      */
982     ListNew(&gameList);
983
984
985     /*
986      * Internet chess server status
987      */
988     if (appData.icsActive) {
989         appData.matchMode = FALSE;
990         appData.matchGames = 0;
991 #if ZIPPY
992         appData.noChessProgram = !appData.zippyPlay;
993 #else
994         appData.zippyPlay = FALSE;
995         appData.zippyTalk = FALSE;
996         appData.noChessProgram = TRUE;
997 #endif
998         if (*appData.icsHelper != NULLCHAR) {
999             appData.useTelnet = TRUE;
1000             appData.telnetProgram = appData.icsHelper;
1001         }
1002     } else {
1003         appData.zippyTalk = appData.zippyPlay = FALSE;
1004     }
1005
1006     /* [AS] Initialize pv info list [HGM] and game state */
1007     {
1008         int i, j;
1009
1010         for( i=0; i<=framePtr; i++ ) {
1011             pvInfoList[i].depth = -1;
1012             boards[i][EP_STATUS] = EP_NONE;
1013             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1014         }
1015     }
1016
1017     InitTimeControls();
1018
1019     /* [AS] Adjudication threshold */
1020     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1021
1022     InitEngine(&first, 0);
1023     InitEngine(&second, 1);
1024     CommonEngineInit();
1025
1026     pairing.which = "pairing"; // pairing engine
1027     pairing.pr = NoProc;
1028     pairing.isr = NULL;
1029     pairing.program = appData.pairingEngine;
1030     pairing.host = "localhost";
1031     pairing.dir = ".";
1032
1033     if (appData.icsActive) {
1034         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1035     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1036         appData.clockMode = FALSE;
1037         first.sendTime = second.sendTime = 0;
1038     }
1039
1040 #if ZIPPY
1041     /* Override some settings from environment variables, for backward
1042        compatibility.  Unfortunately it's not feasible to have the env
1043        vars just set defaults, at least in xboard.  Ugh.
1044     */
1045     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1046       ZippyInit();
1047     }
1048 #endif
1049
1050     if (!appData.icsActive) {
1051       char buf[MSG_SIZ];
1052       int len;
1053
1054       /* Check for variants that are supported only in ICS mode,
1055          or not at all.  Some that are accepted here nevertheless
1056          have bugs; see comments below.
1057       */
1058       VariantClass variant = StringToVariant(appData.variant);
1059       switch (variant) {
1060       case VariantBughouse:     /* need four players and two boards */
1061       case VariantKriegspiel:   /* need to hide pieces and move details */
1062         /* case VariantFischeRandom: (Fabien: moved below) */
1063         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1064         if( (len >= MSG_SIZ) && appData.debugMode )
1065           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1066
1067         DisplayFatalError(buf, 0, 2);
1068         return;
1069
1070       case VariantUnknown:
1071       case VariantLoadable:
1072       case Variant29:
1073       case Variant30:
1074       case Variant31:
1075       case Variant32:
1076       case Variant33:
1077       case Variant34:
1078       case Variant35:
1079       case Variant36:
1080       default:
1081         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1082         if( (len >= MSG_SIZ) && appData.debugMode )
1083           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1084
1085         DisplayFatalError(buf, 0, 2);
1086         return;
1087
1088       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1089       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1090       case VariantGothic:     /* [HGM] should work */
1091       case VariantCapablanca: /* [HGM] should work */
1092       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1093       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1094       case VariantKnightmate: /* [HGM] should work */
1095       case VariantCylinder:   /* [HGM] untested */
1096       case VariantFalcon:     /* [HGM] untested */
1097       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1098                                  offboard interposition not understood */
1099       case VariantNormal:     /* definitely works! */
1100       case VariantWildCastle: /* pieces not automatically shuffled */
1101       case VariantNoCastle:   /* pieces not automatically shuffled */
1102       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1103       case VariantLosers:     /* should work except for win condition,
1104                                  and doesn't know captures are mandatory */
1105       case VariantSuicide:    /* should work except for win condition,
1106                                  and doesn't know captures are mandatory */
1107       case VariantGiveaway:   /* should work except for win condition,
1108                                  and doesn't know captures are mandatory */
1109       case VariantTwoKings:   /* should work */
1110       case VariantAtomic:     /* should work except for win condition */
1111       case Variant3Check:     /* should work except for win condition */
1112       case VariantShatranj:   /* should work except for all win conditions */
1113       case VariantMakruk:     /* should work except for draw countdown */
1114       case VariantBerolina:   /* might work if TestLegality is off */
1115       case VariantCapaRandom: /* should work */
1116       case VariantJanus:      /* should work */
1117       case VariantSuper:      /* experimental */
1118       case VariantGreat:      /* experimental, requires legality testing to be off */
1119       case VariantSChess:     /* S-Chess, should work */
1120       case VariantGrand:      /* should work */
1121       case VariantSpartan:    /* should work */
1122         break;
1123       }
1124     }
1125
1126 }
1127
1128 int
1129 NextIntegerFromString (char ** str, long * value)
1130 {
1131     int result = -1;
1132     char * s = *str;
1133
1134     while( *s == ' ' || *s == '\t' ) {
1135         s++;
1136     }
1137
1138     *value = 0;
1139
1140     if( *s >= '0' && *s <= '9' ) {
1141         while( *s >= '0' && *s <= '9' ) {
1142             *value = *value * 10 + (*s - '0');
1143             s++;
1144         }
1145
1146         result = 0;
1147     }
1148
1149     *str = s;
1150
1151     return result;
1152 }
1153
1154 int
1155 NextTimeControlFromString (char ** str, long * value)
1156 {
1157     long temp;
1158     int result = NextIntegerFromString( str, &temp );
1159
1160     if( result == 0 ) {
1161         *value = temp * 60; /* Minutes */
1162         if( **str == ':' ) {
1163             (*str)++;
1164             result = NextIntegerFromString( str, &temp );
1165             *value += temp; /* Seconds */
1166         }
1167     }
1168
1169     return result;
1170 }
1171
1172 int
1173 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1174 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1175     int result = -1, type = 0; long temp, temp2;
1176
1177     if(**str != ':') return -1; // old params remain in force!
1178     (*str)++;
1179     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1180     if( NextIntegerFromString( str, &temp ) ) return -1;
1181     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1182
1183     if(**str != '/') {
1184         /* time only: incremental or sudden-death time control */
1185         if(**str == '+') { /* increment follows; read it */
1186             (*str)++;
1187             if(**str == '!') type = *(*str)++; // Bronstein TC
1188             if(result = NextIntegerFromString( str, &temp2)) return -1;
1189             *inc = temp2 * 1000;
1190             if(**str == '.') { // read fraction of increment
1191                 char *start = ++(*str);
1192                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1193                 temp2 *= 1000;
1194                 while(start++ < *str) temp2 /= 10;
1195                 *inc += temp2;
1196             }
1197         } else *inc = 0;
1198         *moves = 0; *tc = temp * 1000; *incType = type;
1199         return 0;
1200     }
1201
1202     (*str)++; /* classical time control */
1203     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1204
1205     if(result == 0) {
1206         *moves = temp;
1207         *tc    = temp2 * 1000;
1208         *inc   = 0;
1209         *incType = type;
1210     }
1211     return result;
1212 }
1213
1214 int
1215 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1216 {   /* [HGM] get time to add from the multi-session time-control string */
1217     int incType, moves=1; /* kludge to force reading of first session */
1218     long time, increment;
1219     char *s = tcString;
1220
1221     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1222     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1223     do {
1224         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1225         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1226         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1227         if(movenr == -1) return time;    /* last move before new session     */
1228         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1229         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1230         if(!moves) return increment;     /* current session is incremental   */
1231         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1232     } while(movenr >= -1);               /* try again for next session       */
1233
1234     return 0; // no new time quota on this move
1235 }
1236
1237 int
1238 ParseTimeControl (char *tc, float ti, int mps)
1239 {
1240   long tc1;
1241   long tc2;
1242   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1243   int min, sec=0;
1244
1245   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1246   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1247       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1248   if(ti > 0) {
1249
1250     if(mps)
1251       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1252     else 
1253       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1254   } else {
1255     if(mps)
1256       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1257     else 
1258       snprintf(buf, MSG_SIZ, ":%s", mytc);
1259   }
1260   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1261   
1262   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1263     return FALSE;
1264   }
1265
1266   if( *tc == '/' ) {
1267     /* Parse second time control */
1268     tc++;
1269
1270     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1271       return FALSE;
1272     }
1273
1274     if( tc2 == 0 ) {
1275       return FALSE;
1276     }
1277
1278     timeControl_2 = tc2 * 1000;
1279   }
1280   else {
1281     timeControl_2 = 0;
1282   }
1283
1284   if( tc1 == 0 ) {
1285     return FALSE;
1286   }
1287
1288   timeControl = tc1 * 1000;
1289
1290   if (ti >= 0) {
1291     timeIncrement = ti * 1000;  /* convert to ms */
1292     movesPerSession = 0;
1293   } else {
1294     timeIncrement = 0;
1295     movesPerSession = mps;
1296   }
1297   return TRUE;
1298 }
1299
1300 void
1301 InitBackEnd2 ()
1302 {
1303     if (appData.debugMode) {
1304         fprintf(debugFP, "%s\n", programVersion);
1305     }
1306
1307     set_cont_sequence(appData.wrapContSeq);
1308     if (appData.matchGames > 0) {
1309         appData.matchMode = TRUE;
1310     } else if (appData.matchMode) {
1311         appData.matchGames = 1;
1312     }
1313     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1314         appData.matchGames = appData.sameColorGames;
1315     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1316         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1317         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1318     }
1319     Reset(TRUE, FALSE);
1320     if (appData.noChessProgram || first.protocolVersion == 1) {
1321       InitBackEnd3();
1322     } else {
1323       /* kludge: allow timeout for initial "feature" commands */
1324       FreezeUI();
1325       DisplayMessage("", _("Starting chess program"));
1326       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1327     }
1328 }
1329
1330 int
1331 CalculateIndex (int index, int gameNr)
1332 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1333     int res;
1334     if(index > 0) return index; // fixed nmber
1335     if(index == 0) return 1;
1336     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1337     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1338     return res;
1339 }
1340
1341 int
1342 LoadGameOrPosition (int gameNr)
1343 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1344     if (*appData.loadGameFile != NULLCHAR) {
1345         if (!LoadGameFromFile(appData.loadGameFile,
1346                 CalculateIndex(appData.loadGameIndex, gameNr),
1347                               appData.loadGameFile, FALSE)) {
1348             DisplayFatalError(_("Bad game file"), 0, 1);
1349             return 0;
1350         }
1351     } else if (*appData.loadPositionFile != NULLCHAR) {
1352         if (!LoadPositionFromFile(appData.loadPositionFile,
1353                 CalculateIndex(appData.loadPositionIndex, gameNr),
1354                                   appData.loadPositionFile)) {
1355             DisplayFatalError(_("Bad position file"), 0, 1);
1356             return 0;
1357         }
1358     }
1359     return 1;
1360 }
1361
1362 void
1363 ReserveGame (int gameNr, char resChar)
1364 {
1365     FILE *tf = fopen(appData.tourneyFile, "r+");
1366     char *p, *q, c, buf[MSG_SIZ];
1367     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1368     safeStrCpy(buf, lastMsg, MSG_SIZ);
1369     DisplayMessage(_("Pick new game"), "");
1370     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1371     ParseArgsFromFile(tf);
1372     p = q = appData.results;
1373     if(appData.debugMode) {
1374       char *r = appData.participants;
1375       fprintf(debugFP, "results = '%s'\n", p);
1376       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1377       fprintf(debugFP, "\n");
1378     }
1379     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1380     nextGame = q - p;
1381     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1382     safeStrCpy(q, p, strlen(p) + 2);
1383     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1384     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1385     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1386         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1387         q[nextGame] = '*';
1388     }
1389     fseek(tf, -(strlen(p)+4), SEEK_END);
1390     c = fgetc(tf);
1391     if(c != '"') // depending on DOS or Unix line endings we can be one off
1392          fseek(tf, -(strlen(p)+2), SEEK_END);
1393     else fseek(tf, -(strlen(p)+3), SEEK_END);
1394     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1395     DisplayMessage(buf, "");
1396     free(p); appData.results = q;
1397     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1398        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1399         UnloadEngine(&first);  // next game belongs to other pairing;
1400         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1401     }
1402 }
1403
1404 void
1405 MatchEvent (int mode)
1406 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1407         int dummy;
1408         if(matchMode) { // already in match mode: switch it off
1409             abortMatch = TRUE;
1410             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1411             return;
1412         }
1413 //      if(gameMode != BeginningOfGame) {
1414 //          DisplayError(_("You can only start a match from the initial position."), 0);
1415 //          return;
1416 //      }
1417         abortMatch = FALSE;
1418         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1419         /* Set up machine vs. machine match */
1420         nextGame = 0;
1421         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1422         if(appData.tourneyFile[0]) {
1423             ReserveGame(-1, 0);
1424             if(nextGame > appData.matchGames) {
1425                 char buf[MSG_SIZ];
1426                 if(strchr(appData.results, '*') == NULL) {
1427                     FILE *f;
1428                     appData.tourneyCycles++;
1429                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1430                         fclose(f);
1431                         NextTourneyGame(-1, &dummy);
1432                         ReserveGame(-1, 0);
1433                         if(nextGame <= appData.matchGames) {
1434                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1435                             matchMode = mode;
1436                             ScheduleDelayedEvent(NextMatchGame, 10000);
1437                             return;
1438                         }
1439                     }
1440                 }
1441                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1442                 DisplayError(buf, 0);
1443                 appData.tourneyFile[0] = 0;
1444                 return;
1445             }
1446         } else
1447         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1448             DisplayFatalError(_("Can't have a match with no chess programs"),
1449                               0, 2);
1450             return;
1451         }
1452         matchMode = mode;
1453         matchGame = roundNr = 1;
1454         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1455         NextMatchGame();
1456 }
1457
1458 void
1459 InitBackEnd3 P((void))
1460 {
1461     GameMode initialMode;
1462     char buf[MSG_SIZ];
1463     int err, len;
1464
1465     InitChessProgram(&first, startedFromSetupPosition);
1466
1467     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1468         free(programVersion);
1469         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1470         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1471     }
1472
1473     if (appData.icsActive) {
1474 #ifdef WIN32
1475         /* [DM] Make a console window if needed [HGM] merged ifs */
1476         ConsoleCreate();
1477 #endif
1478         err = establish();
1479         if (err != 0)
1480           {
1481             if (*appData.icsCommPort != NULLCHAR)
1482               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1483                              appData.icsCommPort);
1484             else
1485               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1486                         appData.icsHost, appData.icsPort);
1487
1488             if( (len >= MSG_SIZ) && appData.debugMode )
1489               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1490
1491             DisplayFatalError(buf, err, 1);
1492             return;
1493         }
1494         SetICSMode();
1495         telnetISR =
1496           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1497         fromUserISR =
1498           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1499         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1500             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1501     } else if (appData.noChessProgram) {
1502         SetNCPMode();
1503     } else {
1504         SetGNUMode();
1505     }
1506
1507     if (*appData.cmailGameName != NULLCHAR) {
1508         SetCmailMode();
1509         OpenLoopback(&cmailPR);
1510         cmailISR =
1511           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1512     }
1513
1514     ThawUI();
1515     DisplayMessage("", "");
1516     if (StrCaseCmp(appData.initialMode, "") == 0) {
1517       initialMode = BeginningOfGame;
1518       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1519         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1520         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1521         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1522         ModeHighlight();
1523       }
1524     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1525       initialMode = TwoMachinesPlay;
1526     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1527       initialMode = AnalyzeFile;
1528     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1529       initialMode = AnalyzeMode;
1530     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1531       initialMode = MachinePlaysWhite;
1532     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1533       initialMode = MachinePlaysBlack;
1534     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1535       initialMode = EditGame;
1536     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1537       initialMode = EditPosition;
1538     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1539       initialMode = Training;
1540     } else {
1541       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1542       if( (len >= MSG_SIZ) && appData.debugMode )
1543         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1544
1545       DisplayFatalError(buf, 0, 2);
1546       return;
1547     }
1548
1549     if (appData.matchMode) {
1550         if(appData.tourneyFile[0]) { // start tourney from command line
1551             FILE *f;
1552             if(f = fopen(appData.tourneyFile, "r")) {
1553                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1554                 fclose(f);
1555                 appData.clockMode = TRUE;
1556                 SetGNUMode();
1557             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1558         }
1559         MatchEvent(TRUE);
1560     } else if (*appData.cmailGameName != NULLCHAR) {
1561         /* Set up cmail mode */
1562         ReloadCmailMsgEvent(TRUE);
1563     } else {
1564         /* Set up other modes */
1565         if (initialMode == AnalyzeFile) {
1566           if (*appData.loadGameFile == NULLCHAR) {
1567             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1568             return;
1569           }
1570         }
1571         if (*appData.loadGameFile != NULLCHAR) {
1572             (void) LoadGameFromFile(appData.loadGameFile,
1573                                     appData.loadGameIndex,
1574                                     appData.loadGameFile, TRUE);
1575         } else if (*appData.loadPositionFile != NULLCHAR) {
1576             (void) LoadPositionFromFile(appData.loadPositionFile,
1577                                         appData.loadPositionIndex,
1578                                         appData.loadPositionFile);
1579             /* [HGM] try to make self-starting even after FEN load */
1580             /* to allow automatic setup of fairy variants with wtm */
1581             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1582                 gameMode = BeginningOfGame;
1583                 setboardSpoiledMachineBlack = 1;
1584             }
1585             /* [HGM] loadPos: make that every new game uses the setup */
1586             /* from file as long as we do not switch variant          */
1587             if(!blackPlaysFirst) {
1588                 startedFromPositionFile = TRUE;
1589                 CopyBoard(filePosition, boards[0]);
1590             }
1591         }
1592         if (initialMode == AnalyzeMode) {
1593           if (appData.noChessProgram) {
1594             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1595             return;
1596           }
1597           if (appData.icsActive) {
1598             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1599             return;
1600           }
1601           AnalyzeModeEvent();
1602         } else if (initialMode == AnalyzeFile) {
1603           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1604           ShowThinkingEvent();
1605           AnalyzeFileEvent();
1606           AnalysisPeriodicEvent(1);
1607         } else if (initialMode == MachinePlaysWhite) {
1608           if (appData.noChessProgram) {
1609             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1610                               0, 2);
1611             return;
1612           }
1613           if (appData.icsActive) {
1614             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1615                               0, 2);
1616             return;
1617           }
1618           MachineWhiteEvent();
1619         } else if (initialMode == MachinePlaysBlack) {
1620           if (appData.noChessProgram) {
1621             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1622                               0, 2);
1623             return;
1624           }
1625           if (appData.icsActive) {
1626             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1627                               0, 2);
1628             return;
1629           }
1630           MachineBlackEvent();
1631         } else if (initialMode == TwoMachinesPlay) {
1632           if (appData.noChessProgram) {
1633             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1634                               0, 2);
1635             return;
1636           }
1637           if (appData.icsActive) {
1638             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1639                               0, 2);
1640             return;
1641           }
1642           TwoMachinesEvent();
1643         } else if (initialMode == EditGame) {
1644           EditGameEvent();
1645         } else if (initialMode == EditPosition) {
1646           EditPositionEvent();
1647         } else if (initialMode == Training) {
1648           if (*appData.loadGameFile == NULLCHAR) {
1649             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1650             return;
1651           }
1652           TrainingEvent();
1653         }
1654     }
1655 }
1656
1657 void
1658 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1659 {
1660     DisplayBook(current+1);
1661
1662     MoveHistorySet( movelist, first, last, current, pvInfoList );
1663
1664     EvalGraphSet( first, last, current, pvInfoList );
1665
1666     MakeEngineOutputTitle();
1667 }
1668
1669 /*
1670  * Establish will establish a contact to a remote host.port.
1671  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1672  *  used to talk to the host.
1673  * Returns 0 if okay, error code if not.
1674  */
1675 int
1676 establish ()
1677 {
1678     char buf[MSG_SIZ];
1679
1680     if (*appData.icsCommPort != NULLCHAR) {
1681         /* Talk to the host through a serial comm port */
1682         return OpenCommPort(appData.icsCommPort, &icsPR);
1683
1684     } else if (*appData.gateway != NULLCHAR) {
1685         if (*appData.remoteShell == NULLCHAR) {
1686             /* Use the rcmd protocol to run telnet program on a gateway host */
1687             snprintf(buf, sizeof(buf), "%s %s %s",
1688                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1689             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1690
1691         } else {
1692             /* Use the rsh program to run telnet program on a gateway host */
1693             if (*appData.remoteUser == NULLCHAR) {
1694                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1695                         appData.gateway, appData.telnetProgram,
1696                         appData.icsHost, appData.icsPort);
1697             } else {
1698                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1699                         appData.remoteShell, appData.gateway,
1700                         appData.remoteUser, appData.telnetProgram,
1701                         appData.icsHost, appData.icsPort);
1702             }
1703             return StartChildProcess(buf, "", &icsPR);
1704
1705         }
1706     } else if (appData.useTelnet) {
1707         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1708
1709     } else {
1710         /* TCP socket interface differs somewhat between
1711            Unix and NT; handle details in the front end.
1712            */
1713         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1714     }
1715 }
1716
1717 void
1718 EscapeExpand (char *p, char *q)
1719 {       // [HGM] initstring: routine to shape up string arguments
1720         while(*p++ = *q++) if(p[-1] == '\\')
1721             switch(*q++) {
1722                 case 'n': p[-1] = '\n'; break;
1723                 case 'r': p[-1] = '\r'; break;
1724                 case 't': p[-1] = '\t'; break;
1725                 case '\\': p[-1] = '\\'; break;
1726                 case 0: *p = 0; return;
1727                 default: p[-1] = q[-1]; break;
1728             }
1729 }
1730
1731 void
1732 show_bytes (FILE *fp, char *buf, int count)
1733 {
1734     while (count--) {
1735         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1736             fprintf(fp, "\\%03o", *buf & 0xff);
1737         } else {
1738             putc(*buf, fp);
1739         }
1740         buf++;
1741     }
1742     fflush(fp);
1743 }
1744
1745 /* Returns an errno value */
1746 int
1747 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1748 {
1749     char buf[8192], *p, *q, *buflim;
1750     int left, newcount, outcount;
1751
1752     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1753         *appData.gateway != NULLCHAR) {
1754         if (appData.debugMode) {
1755             fprintf(debugFP, ">ICS: ");
1756             show_bytes(debugFP, message, count);
1757             fprintf(debugFP, "\n");
1758         }
1759         return OutputToProcess(pr, message, count, outError);
1760     }
1761
1762     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1763     p = message;
1764     q = buf;
1765     left = count;
1766     newcount = 0;
1767     while (left) {
1768         if (q >= buflim) {
1769             if (appData.debugMode) {
1770                 fprintf(debugFP, ">ICS: ");
1771                 show_bytes(debugFP, buf, newcount);
1772                 fprintf(debugFP, "\n");
1773             }
1774             outcount = OutputToProcess(pr, buf, newcount, outError);
1775             if (outcount < newcount) return -1; /* to be sure */
1776             q = buf;
1777             newcount = 0;
1778         }
1779         if (*p == '\n') {
1780             *q++ = '\r';
1781             newcount++;
1782         } else if (((unsigned char) *p) == TN_IAC) {
1783             *q++ = (char) TN_IAC;
1784             newcount ++;
1785         }
1786         *q++ = *p++;
1787         newcount++;
1788         left--;
1789     }
1790     if (appData.debugMode) {
1791         fprintf(debugFP, ">ICS: ");
1792         show_bytes(debugFP, buf, newcount);
1793         fprintf(debugFP, "\n");
1794     }
1795     outcount = OutputToProcess(pr, buf, newcount, outError);
1796     if (outcount < newcount) return -1; /* to be sure */
1797     return count;
1798 }
1799
1800 void
1801 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1802 {
1803     int outError, outCount;
1804     static int gotEof = 0;
1805
1806     /* Pass data read from player on to ICS */
1807     if (count > 0) {
1808         gotEof = 0;
1809         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1810         if (outCount < count) {
1811             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1812         }
1813     } else if (count < 0) {
1814         RemoveInputSource(isr);
1815         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1816     } else if (gotEof++ > 0) {
1817         RemoveInputSource(isr);
1818         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1819     }
1820 }
1821
1822 void
1823 KeepAlive ()
1824 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1825     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1826     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1827     SendToICS("date\n");
1828     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1829 }
1830
1831 /* added routine for printf style output to ics */
1832 void
1833 ics_printf (char *format, ...)
1834 {
1835     char buffer[MSG_SIZ];
1836     va_list args;
1837
1838     va_start(args, format);
1839     vsnprintf(buffer, sizeof(buffer), format, args);
1840     buffer[sizeof(buffer)-1] = '\0';
1841     SendToICS(buffer);
1842     va_end(args);
1843 }
1844
1845 void
1846 SendToICS (char *s)
1847 {
1848     int count, outCount, outError;
1849
1850     if (icsPR == NoProc) return;
1851
1852     count = strlen(s);
1853     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1854     if (outCount < count) {
1855         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1856     }
1857 }
1858
1859 /* This is used for sending logon scripts to the ICS. Sending
1860    without a delay causes problems when using timestamp on ICC
1861    (at least on my machine). */
1862 void
1863 SendToICSDelayed (char *s, long msdelay)
1864 {
1865     int count, outCount, outError;
1866
1867     if (icsPR == NoProc) return;
1868
1869     count = strlen(s);
1870     if (appData.debugMode) {
1871         fprintf(debugFP, ">ICS: ");
1872         show_bytes(debugFP, s, count);
1873         fprintf(debugFP, "\n");
1874     }
1875     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1876                                       msdelay);
1877     if (outCount < count) {
1878         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1879     }
1880 }
1881
1882
1883 /* Remove all highlighting escape sequences in s
1884    Also deletes any suffix starting with '('
1885    */
1886 char *
1887 StripHighlightAndTitle (char *s)
1888 {
1889     static char retbuf[MSG_SIZ];
1890     char *p = retbuf;
1891
1892     while (*s != NULLCHAR) {
1893         while (*s == '\033') {
1894             while (*s != NULLCHAR && !isalpha(*s)) s++;
1895             if (*s != NULLCHAR) s++;
1896         }
1897         while (*s != NULLCHAR && *s != '\033') {
1898             if (*s == '(' || *s == '[') {
1899                 *p = NULLCHAR;
1900                 return retbuf;
1901             }
1902             *p++ = *s++;
1903         }
1904     }
1905     *p = NULLCHAR;
1906     return retbuf;
1907 }
1908
1909 /* Remove all highlighting escape sequences in s */
1910 char *
1911 StripHighlight (char *s)
1912 {
1913     static char retbuf[MSG_SIZ];
1914     char *p = retbuf;
1915
1916     while (*s != NULLCHAR) {
1917         while (*s == '\033') {
1918             while (*s != NULLCHAR && !isalpha(*s)) s++;
1919             if (*s != NULLCHAR) s++;
1920         }
1921         while (*s != NULLCHAR && *s != '\033') {
1922             *p++ = *s++;
1923         }
1924     }
1925     *p = NULLCHAR;
1926     return retbuf;
1927 }
1928
1929 char *variantNames[] = VARIANT_NAMES;
1930 char *
1931 VariantName (VariantClass v)
1932 {
1933     return variantNames[v];
1934 }
1935
1936
1937 /* Identify a variant from the strings the chess servers use or the
1938    PGN Variant tag names we use. */
1939 VariantClass
1940 StringToVariant (char *e)
1941 {
1942     char *p;
1943     int wnum = -1;
1944     VariantClass v = VariantNormal;
1945     int i, found = FALSE;
1946     char buf[MSG_SIZ];
1947     int len;
1948
1949     if (!e) return v;
1950
1951     /* [HGM] skip over optional board-size prefixes */
1952     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1953         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1954         while( *e++ != '_');
1955     }
1956
1957     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1958         v = VariantNormal;
1959         found = TRUE;
1960     } else
1961     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1962       if (StrCaseStr(e, variantNames[i])) {
1963         v = (VariantClass) i;
1964         found = TRUE;
1965         break;
1966       }
1967     }
1968
1969     if (!found) {
1970       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1971           || StrCaseStr(e, "wild/fr")
1972           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1973         v = VariantFischeRandom;
1974       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1975                  (i = 1, p = StrCaseStr(e, "w"))) {
1976         p += i;
1977         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1978         if (isdigit(*p)) {
1979           wnum = atoi(p);
1980         } else {
1981           wnum = -1;
1982         }
1983         switch (wnum) {
1984         case 0: /* FICS only, actually */
1985         case 1:
1986           /* Castling legal even if K starts on d-file */
1987           v = VariantWildCastle;
1988           break;
1989         case 2:
1990         case 3:
1991         case 4:
1992           /* Castling illegal even if K & R happen to start in
1993              normal positions. */
1994           v = VariantNoCastle;
1995           break;
1996         case 5:
1997         case 7:
1998         case 8:
1999         case 10:
2000         case 11:
2001         case 12:
2002         case 13:
2003         case 14:
2004         case 15:
2005         case 18:
2006         case 19:
2007           /* Castling legal iff K & R start in normal positions */
2008           v = VariantNormal;
2009           break;
2010         case 6:
2011         case 20:
2012         case 21:
2013           /* Special wilds for position setup; unclear what to do here */
2014           v = VariantLoadable;
2015           break;
2016         case 9:
2017           /* Bizarre ICC game */
2018           v = VariantTwoKings;
2019           break;
2020         case 16:
2021           v = VariantKriegspiel;
2022           break;
2023         case 17:
2024           v = VariantLosers;
2025           break;
2026         case 22:
2027           v = VariantFischeRandom;
2028           break;
2029         case 23:
2030           v = VariantCrazyhouse;
2031           break;
2032         case 24:
2033           v = VariantBughouse;
2034           break;
2035         case 25:
2036           v = Variant3Check;
2037           break;
2038         case 26:
2039           /* Not quite the same as FICS suicide! */
2040           v = VariantGiveaway;
2041           break;
2042         case 27:
2043           v = VariantAtomic;
2044           break;
2045         case 28:
2046           v = VariantShatranj;
2047           break;
2048
2049         /* Temporary names for future ICC types.  The name *will* change in
2050            the next xboard/WinBoard release after ICC defines it. */
2051         case 29:
2052           v = Variant29;
2053           break;
2054         case 30:
2055           v = Variant30;
2056           break;
2057         case 31:
2058           v = Variant31;
2059           break;
2060         case 32:
2061           v = Variant32;
2062           break;
2063         case 33:
2064           v = Variant33;
2065           break;
2066         case 34:
2067           v = Variant34;
2068           break;
2069         case 35:
2070           v = Variant35;
2071           break;
2072         case 36:
2073           v = Variant36;
2074           break;
2075         case 37:
2076           v = VariantShogi;
2077           break;
2078         case 38:
2079           v = VariantXiangqi;
2080           break;
2081         case 39:
2082           v = VariantCourier;
2083           break;
2084         case 40:
2085           v = VariantGothic;
2086           break;
2087         case 41:
2088           v = VariantCapablanca;
2089           break;
2090         case 42:
2091           v = VariantKnightmate;
2092           break;
2093         case 43:
2094           v = VariantFairy;
2095           break;
2096         case 44:
2097           v = VariantCylinder;
2098           break;
2099         case 45:
2100           v = VariantFalcon;
2101           break;
2102         case 46:
2103           v = VariantCapaRandom;
2104           break;
2105         case 47:
2106           v = VariantBerolina;
2107           break;
2108         case 48:
2109           v = VariantJanus;
2110           break;
2111         case 49:
2112           v = VariantSuper;
2113           break;
2114         case 50:
2115           v = VariantGreat;
2116           break;
2117         case -1:
2118           /* Found "wild" or "w" in the string but no number;
2119              must assume it's normal chess. */
2120           v = VariantNormal;
2121           break;
2122         default:
2123           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2124           if( (len >= MSG_SIZ) && appData.debugMode )
2125             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2126
2127           DisplayError(buf, 0);
2128           v = VariantUnknown;
2129           break;
2130         }
2131       }
2132     }
2133     if (appData.debugMode) {
2134       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2135               e, wnum, VariantName(v));
2136     }
2137     return v;
2138 }
2139
2140 static int leftover_start = 0, leftover_len = 0;
2141 char star_match[STAR_MATCH_N][MSG_SIZ];
2142
2143 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2144    advance *index beyond it, and set leftover_start to the new value of
2145    *index; else return FALSE.  If pattern contains the character '*', it
2146    matches any sequence of characters not containing '\r', '\n', or the
2147    character following the '*' (if any), and the matched sequence(s) are
2148    copied into star_match.
2149    */
2150 int
2151 looking_at ( char *buf, int *index, char *pattern)
2152 {
2153     char *bufp = &buf[*index], *patternp = pattern;
2154     int star_count = 0;
2155     char *matchp = star_match[0];
2156
2157     for (;;) {
2158         if (*patternp == NULLCHAR) {
2159             *index = leftover_start = bufp - buf;
2160             *matchp = NULLCHAR;
2161             return TRUE;
2162         }
2163         if (*bufp == NULLCHAR) return FALSE;
2164         if (*patternp == '*') {
2165             if (*bufp == *(patternp + 1)) {
2166                 *matchp = NULLCHAR;
2167                 matchp = star_match[++star_count];
2168                 patternp += 2;
2169                 bufp++;
2170                 continue;
2171             } else if (*bufp == '\n' || *bufp == '\r') {
2172                 patternp++;
2173                 if (*patternp == NULLCHAR)
2174                   continue;
2175                 else
2176                   return FALSE;
2177             } else {
2178                 *matchp++ = *bufp++;
2179                 continue;
2180             }
2181         }
2182         if (*patternp != *bufp) return FALSE;
2183         patternp++;
2184         bufp++;
2185     }
2186 }
2187
2188 void
2189 SendToPlayer (char *data, int length)
2190 {
2191     int error, outCount;
2192     outCount = OutputToProcess(NoProc, data, length, &error);
2193     if (outCount < length) {
2194         DisplayFatalError(_("Error writing to display"), error, 1);
2195     }
2196 }
2197
2198 void
2199 PackHolding (char packed[], char *holding)
2200 {
2201     char *p = holding;
2202     char *q = packed;
2203     int runlength = 0;
2204     int curr = 9999;
2205     do {
2206         if (*p == curr) {
2207             runlength++;
2208         } else {
2209             switch (runlength) {
2210               case 0:
2211                 break;
2212               case 1:
2213                 *q++ = curr;
2214                 break;
2215               case 2:
2216                 *q++ = curr;
2217                 *q++ = curr;
2218                 break;
2219               default:
2220                 sprintf(q, "%d", runlength);
2221                 while (*q) q++;
2222                 *q++ = curr;
2223                 break;
2224             }
2225             runlength = 1;
2226             curr = *p;
2227         }
2228     } while (*p++);
2229     *q = NULLCHAR;
2230 }
2231
2232 /* Telnet protocol requests from the front end */
2233 void
2234 TelnetRequest (unsigned char ddww, unsigned char option)
2235 {
2236     unsigned char msg[3];
2237     int outCount, outError;
2238
2239     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2240
2241     if (appData.debugMode) {
2242         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2243         switch (ddww) {
2244           case TN_DO:
2245             ddwwStr = "DO";
2246             break;
2247           case TN_DONT:
2248             ddwwStr = "DONT";
2249             break;
2250           case TN_WILL:
2251             ddwwStr = "WILL";
2252             break;
2253           case TN_WONT:
2254             ddwwStr = "WONT";
2255             break;
2256           default:
2257             ddwwStr = buf1;
2258             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2259             break;
2260         }
2261         switch (option) {
2262           case TN_ECHO:
2263             optionStr = "ECHO";
2264             break;
2265           default:
2266             optionStr = buf2;
2267             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2268             break;
2269         }
2270         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2271     }
2272     msg[0] = TN_IAC;
2273     msg[1] = ddww;
2274     msg[2] = option;
2275     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2276     if (outCount < 3) {
2277         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2278     }
2279 }
2280
2281 void
2282 DoEcho ()
2283 {
2284     if (!appData.icsActive) return;
2285     TelnetRequest(TN_DO, TN_ECHO);
2286 }
2287
2288 void
2289 DontEcho ()
2290 {
2291     if (!appData.icsActive) return;
2292     TelnetRequest(TN_DONT, TN_ECHO);
2293 }
2294
2295 void
2296 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2297 {
2298     /* put the holdings sent to us by the server on the board holdings area */
2299     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2300     char p;
2301     ChessSquare piece;
2302
2303     if(gameInfo.holdingsWidth < 2)  return;
2304     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2305         return; // prevent overwriting by pre-board holdings
2306
2307     if( (int)lowestPiece >= BlackPawn ) {
2308         holdingsColumn = 0;
2309         countsColumn = 1;
2310         holdingsStartRow = BOARD_HEIGHT-1;
2311         direction = -1;
2312     } else {
2313         holdingsColumn = BOARD_WIDTH-1;
2314         countsColumn = BOARD_WIDTH-2;
2315         holdingsStartRow = 0;
2316         direction = 1;
2317     }
2318
2319     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2320         board[i][holdingsColumn] = EmptySquare;
2321         board[i][countsColumn]   = (ChessSquare) 0;
2322     }
2323     while( (p=*holdings++) != NULLCHAR ) {
2324         piece = CharToPiece( ToUpper(p) );
2325         if(piece == EmptySquare) continue;
2326         /*j = (int) piece - (int) WhitePawn;*/
2327         j = PieceToNumber(piece);
2328         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2329         if(j < 0) continue;               /* should not happen */
2330         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2331         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2332         board[holdingsStartRow+j*direction][countsColumn]++;
2333     }
2334 }
2335
2336
2337 void
2338 VariantSwitch (Board board, VariantClass newVariant)
2339 {
2340    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2341    static Board oldBoard;
2342
2343    startedFromPositionFile = FALSE;
2344    if(gameInfo.variant == newVariant) return;
2345
2346    /* [HGM] This routine is called each time an assignment is made to
2347     * gameInfo.variant during a game, to make sure the board sizes
2348     * are set to match the new variant. If that means adding or deleting
2349     * holdings, we shift the playing board accordingly
2350     * This kludge is needed because in ICS observe mode, we get boards
2351     * of an ongoing game without knowing the variant, and learn about the
2352     * latter only later. This can be because of the move list we requested,
2353     * in which case the game history is refilled from the beginning anyway,
2354     * but also when receiving holdings of a crazyhouse game. In the latter
2355     * case we want to add those holdings to the already received position.
2356     */
2357
2358
2359    if (appData.debugMode) {
2360      fprintf(debugFP, "Switch board from %s to %s\n",
2361              VariantName(gameInfo.variant), VariantName(newVariant));
2362      setbuf(debugFP, NULL);
2363    }
2364    shuffleOpenings = 0;       /* [HGM] shuffle */
2365    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2366    switch(newVariant)
2367      {
2368      case VariantShogi:
2369        newWidth = 9;  newHeight = 9;
2370        gameInfo.holdingsSize = 7;
2371      case VariantBughouse:
2372      case VariantCrazyhouse:
2373        newHoldingsWidth = 2; break;
2374      case VariantGreat:
2375        newWidth = 10;
2376      case VariantSuper:
2377        newHoldingsWidth = 2;
2378        gameInfo.holdingsSize = 8;
2379        break;
2380      case VariantGothic:
2381      case VariantCapablanca:
2382      case VariantCapaRandom:
2383        newWidth = 10;
2384      default:
2385        newHoldingsWidth = gameInfo.holdingsSize = 0;
2386      };
2387
2388    if(newWidth  != gameInfo.boardWidth  ||
2389       newHeight != gameInfo.boardHeight ||
2390       newHoldingsWidth != gameInfo.holdingsWidth ) {
2391
2392      /* shift position to new playing area, if needed */
2393      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2394        for(i=0; i<BOARD_HEIGHT; i++)
2395          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2396            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2397              board[i][j];
2398        for(i=0; i<newHeight; i++) {
2399          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2400          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2401        }
2402      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2403        for(i=0; i<BOARD_HEIGHT; i++)
2404          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2405            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2406              board[i][j];
2407      }
2408      gameInfo.boardWidth  = newWidth;
2409      gameInfo.boardHeight = newHeight;
2410      gameInfo.holdingsWidth = newHoldingsWidth;
2411      gameInfo.variant = newVariant;
2412      InitDrawingSizes(-2, 0);
2413    } else gameInfo.variant = newVariant;
2414    CopyBoard(oldBoard, board);   // remember correctly formatted board
2415      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2416    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2417 }
2418
2419 static int loggedOn = FALSE;
2420
2421 /*-- Game start info cache: --*/
2422 int gs_gamenum;
2423 char gs_kind[MSG_SIZ];
2424 static char player1Name[128] = "";
2425 static char player2Name[128] = "";
2426 static char cont_seq[] = "\n\\   ";
2427 static int player1Rating = -1;
2428 static int player2Rating = -1;
2429 /*----------------------------*/
2430
2431 ColorClass curColor = ColorNormal;
2432 int suppressKibitz = 0;
2433
2434 // [HGM] seekgraph
2435 Boolean soughtPending = FALSE;
2436 Boolean seekGraphUp;
2437 #define MAX_SEEK_ADS 200
2438 #define SQUARE 0x80
2439 char *seekAdList[MAX_SEEK_ADS];
2440 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2441 float tcList[MAX_SEEK_ADS];
2442 char colorList[MAX_SEEK_ADS];
2443 int nrOfSeekAds = 0;
2444 int minRating = 1010, maxRating = 2800;
2445 int hMargin = 10, vMargin = 20, h, w;
2446 extern int squareSize, lineGap;
2447
2448 void
2449 PlotSeekAd (int i)
2450 {
2451         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2452         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2453         if(r < minRating+100 && r >=0 ) r = minRating+100;
2454         if(r > maxRating) r = maxRating;
2455         if(tc < 1.) tc = 1.;
2456         if(tc > 95.) tc = 95.;
2457         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2458         y = ((double)r - minRating)/(maxRating - minRating)
2459             * (h-vMargin-squareSize/8-1) + vMargin;
2460         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2461         if(strstr(seekAdList[i], " u ")) color = 1;
2462         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2463            !strstr(seekAdList[i], "bullet") &&
2464            !strstr(seekAdList[i], "blitz") &&
2465            !strstr(seekAdList[i], "standard") ) color = 2;
2466         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2467         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2468 }
2469
2470 void
2471 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2472 {
2473         char buf[MSG_SIZ], *ext = "";
2474         VariantClass v = StringToVariant(type);
2475         if(strstr(type, "wild")) {
2476             ext = type + 4; // append wild number
2477             if(v == VariantFischeRandom) type = "chess960"; else
2478             if(v == VariantLoadable) type = "setup"; else
2479             type = VariantName(v);
2480         }
2481         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2482         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2483             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2484             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2485             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2486             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2487             seekNrList[nrOfSeekAds] = nr;
2488             zList[nrOfSeekAds] = 0;
2489             seekAdList[nrOfSeekAds++] = StrSave(buf);
2490             if(plot) PlotSeekAd(nrOfSeekAds-1);
2491         }
2492 }
2493
2494 void
2495 EraseSeekDot (int i)
2496 {
2497     int x = xList[i], y = yList[i], d=squareSize/4, k;
2498     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2499     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2500     // now replot every dot that overlapped
2501     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2502         int xx = xList[k], yy = yList[k];
2503         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2504             DrawSeekDot(xx, yy, colorList[k]);
2505     }
2506 }
2507
2508 void
2509 RemoveSeekAd (int nr)
2510 {
2511         int i;
2512         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2513             EraseSeekDot(i);
2514             if(seekAdList[i]) free(seekAdList[i]);
2515             seekAdList[i] = seekAdList[--nrOfSeekAds];
2516             seekNrList[i] = seekNrList[nrOfSeekAds];
2517             ratingList[i] = ratingList[nrOfSeekAds];
2518             colorList[i]  = colorList[nrOfSeekAds];
2519             tcList[i] = tcList[nrOfSeekAds];
2520             xList[i]  = xList[nrOfSeekAds];
2521             yList[i]  = yList[nrOfSeekAds];
2522             zList[i]  = zList[nrOfSeekAds];
2523             seekAdList[nrOfSeekAds] = NULL;
2524             break;
2525         }
2526 }
2527
2528 Boolean
2529 MatchSoughtLine (char *line)
2530 {
2531     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2532     int nr, base, inc, u=0; char dummy;
2533
2534     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2535        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2536        (u=1) &&
2537        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2538         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2539         // match: compact and save the line
2540         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2541         return TRUE;
2542     }
2543     return FALSE;
2544 }
2545
2546 int
2547 DrawSeekGraph ()
2548 {
2549     int i;
2550     if(!seekGraphUp) return FALSE;
2551     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2552     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2553
2554     DrawSeekBackground(0, 0, w, h);
2555     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2556     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2557     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2558         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2559         yy = h-1-yy;
2560         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2561         if(i%500 == 0) {
2562             char buf[MSG_SIZ];
2563             snprintf(buf, MSG_SIZ, "%d", i);
2564             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2565         }
2566     }
2567     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2568     for(i=1; i<100; i+=(i<10?1:5)) {
2569         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2570         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2571         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2572             char buf[MSG_SIZ];
2573             snprintf(buf, MSG_SIZ, "%d", i);
2574             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2575         }
2576     }
2577     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2578     return TRUE;
2579 }
2580
2581 int
2582 SeekGraphClick (ClickType click, int x, int y, int moving)
2583 {
2584     static int lastDown = 0, displayed = 0, lastSecond;
2585     if(y < 0) return FALSE;
2586     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2587         if(click == Release || moving) return FALSE;
2588         nrOfSeekAds = 0;
2589         soughtPending = TRUE;
2590         SendToICS(ics_prefix);
2591         SendToICS("sought\n"); // should this be "sought all"?
2592     } else { // issue challenge based on clicked ad
2593         int dist = 10000; int i, closest = 0, second = 0;
2594         for(i=0; i<nrOfSeekAds; i++) {
2595             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2596             if(d < dist) { dist = d; closest = i; }
2597             second += (d - zList[i] < 120); // count in-range ads
2598             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2599         }
2600         if(dist < 120) {
2601             char buf[MSG_SIZ];
2602             second = (second > 1);
2603             if(displayed != closest || second != lastSecond) {
2604                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2605                 lastSecond = second; displayed = closest;
2606             }
2607             if(click == Press) {
2608                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2609                 lastDown = closest;
2610                 return TRUE;
2611             } // on press 'hit', only show info
2612             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2613             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2614             SendToICS(ics_prefix);
2615             SendToICS(buf);
2616             return TRUE; // let incoming board of started game pop down the graph
2617         } else if(click == Release) { // release 'miss' is ignored
2618             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2619             if(moving == 2) { // right up-click
2620                 nrOfSeekAds = 0; // refresh graph
2621                 soughtPending = TRUE;
2622                 SendToICS(ics_prefix);
2623                 SendToICS("sought\n"); // should this be "sought all"?
2624             }
2625             return TRUE;
2626         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2627         // press miss or release hit 'pop down' seek graph
2628         seekGraphUp = FALSE;
2629         DrawPosition(TRUE, NULL);
2630     }
2631     return TRUE;
2632 }
2633
2634 void
2635 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2636 {
2637 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2638 #define STARTED_NONE 0
2639 #define STARTED_MOVES 1
2640 #define STARTED_BOARD 2
2641 #define STARTED_OBSERVE 3
2642 #define STARTED_HOLDINGS 4
2643 #define STARTED_CHATTER 5
2644 #define STARTED_COMMENT 6
2645 #define STARTED_MOVES_NOHIDE 7
2646
2647     static int started = STARTED_NONE;
2648     static char parse[20000];
2649     static int parse_pos = 0;
2650     static char buf[BUF_SIZE + 1];
2651     static int firstTime = TRUE, intfSet = FALSE;
2652     static ColorClass prevColor = ColorNormal;
2653     static int savingComment = FALSE;
2654     static int cmatch = 0; // continuation sequence match
2655     char *bp;
2656     char str[MSG_SIZ];
2657     int i, oldi;
2658     int buf_len;
2659     int next_out;
2660     int tkind;
2661     int backup;    /* [DM] For zippy color lines */
2662     char *p;
2663     char talker[MSG_SIZ]; // [HGM] chat
2664     int channel;
2665
2666     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2667
2668     if (appData.debugMode) {
2669       if (!error) {
2670         fprintf(debugFP, "<ICS: ");
2671         show_bytes(debugFP, data, count);
2672         fprintf(debugFP, "\n");
2673       }
2674     }
2675
2676     if (appData.debugMode) { int f = forwardMostMove;
2677         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2678                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2679                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2680     }
2681     if (count > 0) {
2682         /* If last read ended with a partial line that we couldn't parse,
2683            prepend it to the new read and try again. */
2684         if (leftover_len > 0) {
2685             for (i=0; i<leftover_len; i++)
2686               buf[i] = buf[leftover_start + i];
2687         }
2688
2689     /* copy new characters into the buffer */
2690     bp = buf + leftover_len;
2691     buf_len=leftover_len;
2692     for (i=0; i<count; i++)
2693     {
2694         // ignore these
2695         if (data[i] == '\r')
2696             continue;
2697
2698         // join lines split by ICS?
2699         if (!appData.noJoin)
2700         {
2701             /*
2702                 Joining just consists of finding matches against the
2703                 continuation sequence, and discarding that sequence
2704                 if found instead of copying it.  So, until a match
2705                 fails, there's nothing to do since it might be the
2706                 complete sequence, and thus, something we don't want
2707                 copied.
2708             */
2709             if (data[i] == cont_seq[cmatch])
2710             {
2711                 cmatch++;
2712                 if (cmatch == strlen(cont_seq))
2713                 {
2714                     cmatch = 0; // complete match.  just reset the counter
2715
2716                     /*
2717                         it's possible for the ICS to not include the space
2718                         at the end of the last word, making our [correct]
2719                         join operation fuse two separate words.  the server
2720                         does this when the space occurs at the width setting.
2721                     */
2722                     if (!buf_len || buf[buf_len-1] != ' ')
2723                     {
2724                         *bp++ = ' ';
2725                         buf_len++;
2726                     }
2727                 }
2728                 continue;
2729             }
2730             else if (cmatch)
2731             {
2732                 /*
2733                     match failed, so we have to copy what matched before
2734                     falling through and copying this character.  In reality,
2735                     this will only ever be just the newline character, but
2736                     it doesn't hurt to be precise.
2737                 */
2738                 strncpy(bp, cont_seq, cmatch);
2739                 bp += cmatch;
2740                 buf_len += cmatch;
2741                 cmatch = 0;
2742             }
2743         }
2744
2745         // copy this char
2746         *bp++ = data[i];
2747         buf_len++;
2748     }
2749
2750         buf[buf_len] = NULLCHAR;
2751 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2752         next_out = 0;
2753         leftover_start = 0;
2754
2755         i = 0;
2756         while (i < buf_len) {
2757             /* Deal with part of the TELNET option negotiation
2758                protocol.  We refuse to do anything beyond the
2759                defaults, except that we allow the WILL ECHO option,
2760                which ICS uses to turn off password echoing when we are
2761                directly connected to it.  We reject this option
2762                if localLineEditing mode is on (always on in xboard)
2763                and we are talking to port 23, which might be a real
2764                telnet server that will try to keep WILL ECHO on permanently.
2765              */
2766             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2767                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2768                 unsigned char option;
2769                 oldi = i;
2770                 switch ((unsigned char) buf[++i]) {
2771                   case TN_WILL:
2772                     if (appData.debugMode)
2773                       fprintf(debugFP, "\n<WILL ");
2774                     switch (option = (unsigned char) buf[++i]) {
2775                       case TN_ECHO:
2776                         if (appData.debugMode)
2777                           fprintf(debugFP, "ECHO ");
2778                         /* Reply only if this is a change, according
2779                            to the protocol rules. */
2780                         if (remoteEchoOption) break;
2781                         if (appData.localLineEditing &&
2782                             atoi(appData.icsPort) == TN_PORT) {
2783                             TelnetRequest(TN_DONT, TN_ECHO);
2784                         } else {
2785                             EchoOff();
2786                             TelnetRequest(TN_DO, TN_ECHO);
2787                             remoteEchoOption = TRUE;
2788                         }
2789                         break;
2790                       default:
2791                         if (appData.debugMode)
2792                           fprintf(debugFP, "%d ", option);
2793                         /* Whatever this is, we don't want it. */
2794                         TelnetRequest(TN_DONT, option);
2795                         break;
2796                     }
2797                     break;
2798                   case TN_WONT:
2799                     if (appData.debugMode)
2800                       fprintf(debugFP, "\n<WONT ");
2801                     switch (option = (unsigned char) buf[++i]) {
2802                       case TN_ECHO:
2803                         if (appData.debugMode)
2804                           fprintf(debugFP, "ECHO ");
2805                         /* Reply only if this is a change, according
2806                            to the protocol rules. */
2807                         if (!remoteEchoOption) break;
2808                         EchoOn();
2809                         TelnetRequest(TN_DONT, TN_ECHO);
2810                         remoteEchoOption = FALSE;
2811                         break;
2812                       default:
2813                         if (appData.debugMode)
2814                           fprintf(debugFP, "%d ", (unsigned char) option);
2815                         /* Whatever this is, it must already be turned
2816                            off, because we never agree to turn on
2817                            anything non-default, so according to the
2818                            protocol rules, we don't reply. */
2819                         break;
2820                     }
2821                     break;
2822                   case TN_DO:
2823                     if (appData.debugMode)
2824                       fprintf(debugFP, "\n<DO ");
2825                     switch (option = (unsigned char) buf[++i]) {
2826                       default:
2827                         /* Whatever this is, we refuse to do it. */
2828                         if (appData.debugMode)
2829                           fprintf(debugFP, "%d ", option);
2830                         TelnetRequest(TN_WONT, option);
2831                         break;
2832                     }
2833                     break;
2834                   case TN_DONT:
2835                     if (appData.debugMode)
2836                       fprintf(debugFP, "\n<DONT ");
2837                     switch (option = (unsigned char) buf[++i]) {
2838                       default:
2839                         if (appData.debugMode)
2840                           fprintf(debugFP, "%d ", option);
2841                         /* Whatever this is, we are already not doing
2842                            it, because we never agree to do anything
2843                            non-default, so according to the protocol
2844                            rules, we don't reply. */
2845                         break;
2846                     }
2847                     break;
2848                   case TN_IAC:
2849                     if (appData.debugMode)
2850                       fprintf(debugFP, "\n<IAC ");
2851                     /* Doubled IAC; pass it through */
2852                     i--;
2853                     break;
2854                   default:
2855                     if (appData.debugMode)
2856                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2857                     /* Drop all other telnet commands on the floor */
2858                     break;
2859                 }
2860                 if (oldi > next_out)
2861                   SendToPlayer(&buf[next_out], oldi - next_out);
2862                 if (++i > next_out)
2863                   next_out = i;
2864                 continue;
2865             }
2866
2867             /* OK, this at least will *usually* work */
2868             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2869                 loggedOn = TRUE;
2870             }
2871
2872             if (loggedOn && !intfSet) {
2873                 if (ics_type == ICS_ICC) {
2874                   snprintf(str, MSG_SIZ,
2875                           "/set-quietly interface %s\n/set-quietly style 12\n",
2876                           programVersion);
2877                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2878                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2879                 } else if (ics_type == ICS_CHESSNET) {
2880                   snprintf(str, MSG_SIZ, "/style 12\n");
2881                 } else {
2882                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2883                   strcat(str, programVersion);
2884                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2885                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2886                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2887 #ifdef WIN32
2888                   strcat(str, "$iset nohighlight 1\n");
2889 #endif
2890                   strcat(str, "$iset lock 1\n$style 12\n");
2891                 }
2892                 SendToICS(str);
2893                 NotifyFrontendLogin();
2894                 intfSet = TRUE;
2895             }
2896
2897             if (started == STARTED_COMMENT) {
2898                 /* Accumulate characters in comment */
2899                 parse[parse_pos++] = buf[i];
2900                 if (buf[i] == '\n') {
2901                     parse[parse_pos] = NULLCHAR;
2902                     if(chattingPartner>=0) {
2903                         char mess[MSG_SIZ];
2904                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2905                         OutputChatMessage(chattingPartner, mess);
2906                         chattingPartner = -1;
2907                         next_out = i+1; // [HGM] suppress printing in ICS window
2908                     } else
2909                     if(!suppressKibitz) // [HGM] kibitz
2910                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2911                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2912                         int nrDigit = 0, nrAlph = 0, j;
2913                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2914                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2915                         parse[parse_pos] = NULLCHAR;
2916                         // try to be smart: if it does not look like search info, it should go to
2917                         // ICS interaction window after all, not to engine-output window.
2918                         for(j=0; j<parse_pos; j++) { // count letters and digits
2919                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2920                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2921                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2922                         }
2923                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2924                             int depth=0; float score;
2925                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2926                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2927                                 pvInfoList[forwardMostMove-1].depth = depth;
2928                                 pvInfoList[forwardMostMove-1].score = 100*score;
2929                             }
2930                             OutputKibitz(suppressKibitz, parse);
2931                         } else {
2932                             char tmp[MSG_SIZ];
2933                             if(gameMode == IcsObserving) // restore original ICS messages
2934                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2935                             else
2936                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2937                             SendToPlayer(tmp, strlen(tmp));
2938                         }
2939                         next_out = i+1; // [HGM] suppress printing in ICS window
2940                     }
2941                     started = STARTED_NONE;
2942                 } else {
2943                     /* Don't match patterns against characters in comment */
2944                     i++;
2945                     continue;
2946                 }
2947             }
2948             if (started == STARTED_CHATTER) {
2949                 if (buf[i] != '\n') {
2950                     /* Don't match patterns against characters in chatter */
2951                     i++;
2952                     continue;
2953                 }
2954                 started = STARTED_NONE;
2955                 if(suppressKibitz) next_out = i+1;
2956             }
2957
2958             /* Kludge to deal with rcmd protocol */
2959             if (firstTime && looking_at(buf, &i, "\001*")) {
2960                 DisplayFatalError(&buf[1], 0, 1);
2961                 continue;
2962             } else {
2963                 firstTime = FALSE;
2964             }
2965
2966             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2967                 ics_type = ICS_ICC;
2968                 ics_prefix = "/";
2969                 if (appData.debugMode)
2970                   fprintf(debugFP, "ics_type %d\n", ics_type);
2971                 continue;
2972             }
2973             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2974                 ics_type = ICS_FICS;
2975                 ics_prefix = "$";
2976                 if (appData.debugMode)
2977                   fprintf(debugFP, "ics_type %d\n", ics_type);
2978                 continue;
2979             }
2980             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2981                 ics_type = ICS_CHESSNET;
2982                 ics_prefix = "/";
2983                 if (appData.debugMode)
2984                   fprintf(debugFP, "ics_type %d\n", ics_type);
2985                 continue;
2986             }
2987
2988             if (!loggedOn &&
2989                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2990                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2991                  looking_at(buf, &i, "will be \"*\""))) {
2992               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2993               continue;
2994             }
2995
2996             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2997               char buf[MSG_SIZ];
2998               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2999               DisplayIcsInteractionTitle(buf);
3000               have_set_title = TRUE;
3001             }
3002
3003             /* skip finger notes */
3004             if (started == STARTED_NONE &&
3005                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3006                  (buf[i] == '1' && buf[i+1] == '0')) &&
3007                 buf[i+2] == ':' && buf[i+3] == ' ') {
3008               started = STARTED_CHATTER;
3009               i += 3;
3010               continue;
3011             }
3012
3013             oldi = i;
3014             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3015             if(appData.seekGraph) {
3016                 if(soughtPending && MatchSoughtLine(buf+i)) {
3017                     i = strstr(buf+i, "rated") - buf;
3018                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3019                     next_out = leftover_start = i;
3020                     started = STARTED_CHATTER;
3021                     suppressKibitz = TRUE;
3022                     continue;
3023                 }
3024                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3025                         && looking_at(buf, &i, "* ads displayed")) {
3026                     soughtPending = FALSE;
3027                     seekGraphUp = TRUE;
3028                     DrawSeekGraph();
3029                     continue;
3030                 }
3031                 if(appData.autoRefresh) {
3032                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3033                         int s = (ics_type == ICS_ICC); // ICC format differs
3034                         if(seekGraphUp)
3035                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3036                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3037                         looking_at(buf, &i, "*% "); // eat prompt
3038                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3039                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3040                         next_out = i; // suppress
3041                         continue;
3042                     }
3043                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3044                         char *p = star_match[0];
3045                         while(*p) {
3046                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3047                             while(*p && *p++ != ' '); // next
3048                         }
3049                         looking_at(buf, &i, "*% "); // eat prompt
3050                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3051                         next_out = i;
3052                         continue;
3053                     }
3054                 }
3055             }
3056
3057             /* skip formula vars */
3058             if (started == STARTED_NONE &&
3059                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3060               started = STARTED_CHATTER;
3061               i += 3;
3062               continue;
3063             }
3064
3065             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3066             if (appData.autoKibitz && started == STARTED_NONE &&
3067                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3068                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3069                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3070                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3071                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3072                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3073                         suppressKibitz = TRUE;
3074                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3075                         next_out = i;
3076                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3077                                 && (gameMode == IcsPlayingWhite)) ||
3078                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3079                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3080                             started = STARTED_CHATTER; // own kibitz we simply discard
3081                         else {
3082                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3083                             parse_pos = 0; parse[0] = NULLCHAR;
3084                             savingComment = TRUE;
3085                             suppressKibitz = gameMode != IcsObserving ? 2 :
3086                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3087                         }
3088                         continue;
3089                 } else
3090                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3091                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3092                          && atoi(star_match[0])) {
3093                     // suppress the acknowledgements of our own autoKibitz
3094                     char *p;
3095                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3096                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3097                     SendToPlayer(star_match[0], strlen(star_match[0]));
3098                     if(looking_at(buf, &i, "*% ")) // eat prompt
3099                         suppressKibitz = FALSE;
3100                     next_out = i;
3101                     continue;
3102                 }
3103             } // [HGM] kibitz: end of patch
3104
3105             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3106
3107             // [HGM] chat: intercept tells by users for which we have an open chat window
3108             channel = -1;
3109             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3110                                            looking_at(buf, &i, "* whispers:") ||
3111                                            looking_at(buf, &i, "* kibitzes:") ||
3112                                            looking_at(buf, &i, "* shouts:") ||
3113                                            looking_at(buf, &i, "* c-shouts:") ||
3114                                            looking_at(buf, &i, "--> * ") ||
3115                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3116                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3117                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3118                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3119                 int p;
3120                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3121                 chattingPartner = -1;
3122
3123                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3124                 for(p=0; p<MAX_CHAT; p++) {
3125                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3126                     talker[0] = '['; strcat(talker, "] ");
3127                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3128                     chattingPartner = p; break;
3129                     }
3130                 } else
3131                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3132                 for(p=0; p<MAX_CHAT; p++) {
3133                     if(!strcmp("kibitzes", chatPartner[p])) {
3134                         talker[0] = '['; strcat(talker, "] ");
3135                         chattingPartner = p; break;
3136                     }
3137                 } else
3138                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3139                 for(p=0; p<MAX_CHAT; p++) {
3140                     if(!strcmp("whispers", chatPartner[p])) {
3141                         talker[0] = '['; strcat(talker, "] ");
3142                         chattingPartner = p; break;
3143                     }
3144                 } else
3145                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3146                   if(buf[i-8] == '-' && buf[i-3] == 't')
3147                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3148                     if(!strcmp("c-shouts", chatPartner[p])) {
3149                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3150                         chattingPartner = p; break;
3151                     }
3152                   }
3153                   if(chattingPartner < 0)
3154                   for(p=0; p<MAX_CHAT; p++) {
3155                     if(!strcmp("shouts", chatPartner[p])) {
3156                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3157                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3158                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3159                         chattingPartner = p; break;
3160                     }
3161                   }
3162                 }
3163                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3164                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3165                     talker[0] = 0; Colorize(ColorTell, FALSE);
3166                     chattingPartner = p; break;
3167                 }
3168                 if(chattingPartner<0) i = oldi; else {
3169                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3170                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3171                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3172                     started = STARTED_COMMENT;
3173                     parse_pos = 0; parse[0] = NULLCHAR;
3174                     savingComment = 3 + chattingPartner; // counts as TRUE
3175                     suppressKibitz = TRUE;
3176                     continue;
3177                 }
3178             } // [HGM] chat: end of patch
3179
3180           backup = i;
3181             if (appData.zippyTalk || appData.zippyPlay) {
3182                 /* [DM] Backup address for color zippy lines */
3183 #if ZIPPY
3184                if (loggedOn == TRUE)
3185                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3186                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3187 #endif
3188             } // [DM] 'else { ' deleted
3189                 if (
3190                     /* Regular tells and says */
3191                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3192                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3193                     looking_at(buf, &i, "* says: ") ||
3194                     /* Don't color "message" or "messages" output */
3195                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3196                     looking_at(buf, &i, "*. * at *:*: ") ||
3197                     looking_at(buf, &i, "--* (*:*): ") ||
3198                     /* Message notifications (same color as tells) */
3199                     looking_at(buf, &i, "* has left a message ") ||
3200                     looking_at(buf, &i, "* just sent you a message:\n") ||
3201                     /* Whispers and kibitzes */
3202                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3203                     looking_at(buf, &i, "* kibitzes: ") ||
3204                     /* Channel tells */
3205                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3206
3207                   if (tkind == 1 && strchr(star_match[0], ':')) {
3208                       /* Avoid "tells you:" spoofs in channels */
3209                      tkind = 3;
3210                   }
3211                   if (star_match[0][0] == NULLCHAR ||
3212                       strchr(star_match[0], ' ') ||
3213                       (tkind == 3 && strchr(star_match[1], ' '))) {
3214                     /* Reject bogus matches */
3215                     i = oldi;
3216                   } else {
3217                     if (appData.colorize) {
3218                       if (oldi > next_out) {
3219                         SendToPlayer(&buf[next_out], oldi - next_out);
3220                         next_out = oldi;
3221                       }
3222                       switch (tkind) {
3223                       case 1:
3224                         Colorize(ColorTell, FALSE);
3225                         curColor = ColorTell;
3226                         break;
3227                       case 2:
3228                         Colorize(ColorKibitz, FALSE);
3229                         curColor = ColorKibitz;
3230                         break;
3231                       case 3:
3232                         p = strrchr(star_match[1], '(');
3233                         if (p == NULL) {
3234                           p = star_match[1];
3235                         } else {
3236                           p++;
3237                         }
3238                         if (atoi(p) == 1) {
3239                           Colorize(ColorChannel1, FALSE);
3240                           curColor = ColorChannel1;
3241                         } else {
3242                           Colorize(ColorChannel, FALSE);
3243                           curColor = ColorChannel;
3244                         }
3245                         break;
3246                       case 5:
3247                         curColor = ColorNormal;
3248                         break;
3249                       }
3250                     }
3251                     if (started == STARTED_NONE && appData.autoComment &&
3252                         (gameMode == IcsObserving ||
3253                          gameMode == IcsPlayingWhite ||
3254                          gameMode == IcsPlayingBlack)) {
3255                       parse_pos = i - oldi;
3256                       memcpy(parse, &buf[oldi], parse_pos);
3257                       parse[parse_pos] = NULLCHAR;
3258                       started = STARTED_COMMENT;
3259                       savingComment = TRUE;
3260                     } else {
3261                       started = STARTED_CHATTER;
3262                       savingComment = FALSE;
3263                     }
3264                     loggedOn = TRUE;
3265                     continue;
3266                   }
3267                 }
3268
3269                 if (looking_at(buf, &i, "* s-shouts: ") ||
3270                     looking_at(buf, &i, "* c-shouts: ")) {
3271                     if (appData.colorize) {
3272                         if (oldi > next_out) {
3273                             SendToPlayer(&buf[next_out], oldi - next_out);
3274                             next_out = oldi;
3275                         }
3276                         Colorize(ColorSShout, FALSE);
3277                         curColor = ColorSShout;
3278                     }
3279                     loggedOn = TRUE;
3280                     started = STARTED_CHATTER;
3281                     continue;
3282                 }
3283
3284                 if (looking_at(buf, &i, "--->")) {
3285                     loggedOn = TRUE;
3286                     continue;
3287                 }
3288
3289                 if (looking_at(buf, &i, "* shouts: ") ||
3290                     looking_at(buf, &i, "--> ")) {
3291                     if (appData.colorize) {
3292                         if (oldi > next_out) {
3293                             SendToPlayer(&buf[next_out], oldi - next_out);
3294                             next_out = oldi;
3295                         }
3296                         Colorize(ColorShout, FALSE);
3297                         curColor = ColorShout;
3298                     }
3299                     loggedOn = TRUE;
3300                     started = STARTED_CHATTER;
3301                     continue;
3302                 }
3303
3304                 if (looking_at( buf, &i, "Challenge:")) {
3305                     if (appData.colorize) {
3306                         if (oldi > next_out) {
3307                             SendToPlayer(&buf[next_out], oldi - next_out);
3308                             next_out = oldi;
3309                         }
3310                         Colorize(ColorChallenge, FALSE);
3311                         curColor = ColorChallenge;
3312                     }
3313                     loggedOn = TRUE;
3314                     continue;
3315                 }
3316
3317                 if (looking_at(buf, &i, "* offers you") ||
3318                     looking_at(buf, &i, "* offers to be") ||
3319                     looking_at(buf, &i, "* would like to") ||
3320                     looking_at(buf, &i, "* requests to") ||
3321                     looking_at(buf, &i, "Your opponent offers") ||
3322                     looking_at(buf, &i, "Your opponent requests")) {
3323
3324                     if (appData.colorize) {
3325                         if (oldi > next_out) {
3326                             SendToPlayer(&buf[next_out], oldi - next_out);
3327                             next_out = oldi;
3328                         }
3329                         Colorize(ColorRequest, FALSE);
3330                         curColor = ColorRequest;
3331                     }
3332                     continue;
3333                 }
3334
3335                 if (looking_at(buf, &i, "* (*) seeking")) {
3336                     if (appData.colorize) {
3337                         if (oldi > next_out) {
3338                             SendToPlayer(&buf[next_out], oldi - next_out);
3339                             next_out = oldi;
3340                         }
3341                         Colorize(ColorSeek, FALSE);
3342                         curColor = ColorSeek;
3343                     }
3344                     continue;
3345             }
3346
3347           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3348
3349             if (looking_at(buf, &i, "\\   ")) {
3350                 if (prevColor != ColorNormal) {
3351                     if (oldi > next_out) {
3352                         SendToPlayer(&buf[next_out], oldi - next_out);
3353                         next_out = oldi;
3354                     }
3355                     Colorize(prevColor, TRUE);
3356                     curColor = prevColor;
3357                 }
3358                 if (savingComment) {
3359                     parse_pos = i - oldi;
3360                     memcpy(parse, &buf[oldi], parse_pos);
3361                     parse[parse_pos] = NULLCHAR;
3362                     started = STARTED_COMMENT;
3363                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3364                         chattingPartner = savingComment - 3; // kludge to remember the box
3365                 } else {
3366                     started = STARTED_CHATTER;
3367                 }
3368                 continue;
3369             }
3370
3371             if (looking_at(buf, &i, "Black Strength :") ||
3372                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3373                 looking_at(buf, &i, "<10>") ||
3374                 looking_at(buf, &i, "#@#")) {
3375                 /* Wrong board style */
3376                 loggedOn = TRUE;
3377                 SendToICS(ics_prefix);
3378                 SendToICS("set style 12\n");
3379                 SendToICS(ics_prefix);
3380                 SendToICS("refresh\n");
3381                 continue;
3382             }
3383
3384             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3385                 ICSInitScript();
3386                 have_sent_ICS_logon = 1;
3387                 continue;
3388             }
3389
3390             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3391                 (looking_at(buf, &i, "\n<12> ") ||
3392                  looking_at(buf, &i, "<12> "))) {
3393                 loggedOn = TRUE;
3394                 if (oldi > next_out) {
3395                     SendToPlayer(&buf[next_out], oldi - next_out);
3396                 }
3397                 next_out = i;
3398                 started = STARTED_BOARD;
3399                 parse_pos = 0;
3400                 continue;
3401             }
3402
3403             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3404                 looking_at(buf, &i, "<b1> ")) {
3405                 if (oldi > next_out) {
3406                     SendToPlayer(&buf[next_out], oldi - next_out);
3407                 }
3408                 next_out = i;
3409                 started = STARTED_HOLDINGS;
3410                 parse_pos = 0;
3411                 continue;
3412             }
3413
3414             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3415                 loggedOn = TRUE;
3416                 /* Header for a move list -- first line */
3417
3418                 switch (ics_getting_history) {
3419                   case H_FALSE:
3420                     switch (gameMode) {
3421                       case IcsIdle:
3422                       case BeginningOfGame:
3423                         /* User typed "moves" or "oldmoves" while we
3424                            were idle.  Pretend we asked for these
3425                            moves and soak them up so user can step
3426                            through them and/or save them.
3427                            */
3428                         Reset(FALSE, TRUE);
3429                         gameMode = IcsObserving;
3430                         ModeHighlight();
3431                         ics_gamenum = -1;
3432                         ics_getting_history = H_GOT_UNREQ_HEADER;
3433                         break;
3434                       case EditGame: /*?*/
3435                       case EditPosition: /*?*/
3436                         /* Should above feature work in these modes too? */
3437                         /* For now it doesn't */
3438                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3439                         break;
3440                       default:
3441                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3442                         break;
3443                     }
3444                     break;
3445                   case H_REQUESTED:
3446                     /* Is this the right one? */
3447                     if (gameInfo.white && gameInfo.black &&
3448                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3449                         strcmp(gameInfo.black, star_match[2]) == 0) {
3450                         /* All is well */
3451                         ics_getting_history = H_GOT_REQ_HEADER;
3452                     }
3453                     break;
3454                   case H_GOT_REQ_HEADER:
3455                   case H_GOT_UNREQ_HEADER:
3456                   case H_GOT_UNWANTED_HEADER:
3457                   case H_GETTING_MOVES:
3458                     /* Should not happen */
3459                     DisplayError(_("Error gathering move list: two headers"), 0);
3460                     ics_getting_history = H_FALSE;
3461                     break;
3462                 }
3463
3464                 /* Save player ratings into gameInfo if needed */
3465                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3466                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3467                     (gameInfo.whiteRating == -1 ||
3468                      gameInfo.blackRating == -1)) {
3469
3470                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3471                     gameInfo.blackRating = string_to_rating(star_match[3]);
3472                     if (appData.debugMode)
3473                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3474                               gameInfo.whiteRating, gameInfo.blackRating);
3475                 }
3476                 continue;
3477             }
3478
3479             if (looking_at(buf, &i,
3480               "* * match, initial time: * minute*, increment: * second")) {
3481                 /* Header for a move list -- second line */
3482                 /* Initial board will follow if this is a wild game */
3483                 if (gameInfo.event != NULL) free(gameInfo.event);
3484                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3485                 gameInfo.event = StrSave(str);
3486                 /* [HGM] we switched variant. Translate boards if needed. */
3487                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3488                 continue;
3489             }
3490
3491             if (looking_at(buf, &i, "Move  ")) {
3492                 /* Beginning of a move list */
3493                 switch (ics_getting_history) {
3494                   case H_FALSE:
3495                     /* Normally should not happen */
3496                     /* Maybe user hit reset while we were parsing */
3497                     break;
3498                   case H_REQUESTED:
3499                     /* Happens if we are ignoring a move list that is not
3500                      * the one we just requested.  Common if the user
3501                      * tries to observe two games without turning off
3502                      * getMoveList */
3503                     break;
3504                   case H_GETTING_MOVES:
3505                     /* Should not happen */
3506                     DisplayError(_("Error gathering move list: nested"), 0);
3507                     ics_getting_history = H_FALSE;
3508                     break;
3509                   case H_GOT_REQ_HEADER:
3510                     ics_getting_history = H_GETTING_MOVES;
3511                     started = STARTED_MOVES;
3512                     parse_pos = 0;
3513                     if (oldi > next_out) {
3514                         SendToPlayer(&buf[next_out], oldi - next_out);
3515                     }
3516                     break;
3517                   case H_GOT_UNREQ_HEADER:
3518                     ics_getting_history = H_GETTING_MOVES;
3519                     started = STARTED_MOVES_NOHIDE;
3520                     parse_pos = 0;
3521                     break;
3522                   case H_GOT_UNWANTED_HEADER:
3523                     ics_getting_history = H_FALSE;
3524                     break;
3525                 }
3526                 continue;
3527             }
3528
3529             if (looking_at(buf, &i, "% ") ||
3530                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3531                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3532                 if(soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3533                     soughtPending = FALSE;
3534                     seekGraphUp = TRUE;
3535                     DrawSeekGraph();
3536                 }
3537                 if(suppressKibitz) next_out = i;
3538                 savingComment = FALSE;
3539                 suppressKibitz = 0;
3540                 switch (started) {
3541                   case STARTED_MOVES:
3542                   case STARTED_MOVES_NOHIDE:
3543                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3544                     parse[parse_pos + i - oldi] = NULLCHAR;
3545                     ParseGameHistory(parse);
3546 #if ZIPPY
3547                     if (appData.zippyPlay && first.initDone) {
3548                         FeedMovesToProgram(&first, forwardMostMove);
3549                         if (gameMode == IcsPlayingWhite) {
3550                             if (WhiteOnMove(forwardMostMove)) {
3551                                 if (first.sendTime) {
3552                                   if (first.useColors) {
3553                                     SendToProgram("black\n", &first);
3554                                   }
3555                                   SendTimeRemaining(&first, TRUE);
3556                                 }
3557                                 if (first.useColors) {
3558                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3559                                 }
3560                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3561                                 first.maybeThinking = TRUE;
3562                             } else {
3563                                 if (first.usePlayother) {
3564                                   if (first.sendTime) {
3565                                     SendTimeRemaining(&first, TRUE);
3566                                   }
3567                                   SendToProgram("playother\n", &first);
3568                                   firstMove = FALSE;
3569                                 } else {
3570                                   firstMove = TRUE;
3571                                 }
3572                             }
3573                         } else if (gameMode == IcsPlayingBlack) {
3574                             if (!WhiteOnMove(forwardMostMove)) {
3575                                 if (first.sendTime) {
3576                                   if (first.useColors) {
3577                                     SendToProgram("white\n", &first);
3578                                   }
3579                                   SendTimeRemaining(&first, FALSE);
3580                                 }
3581                                 if (first.useColors) {
3582                                   SendToProgram("black\n", &first);
3583                                 }
3584                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3585                                 first.maybeThinking = TRUE;
3586                             } else {
3587                                 if (first.usePlayother) {
3588                                   if (first.sendTime) {
3589                                     SendTimeRemaining(&first, FALSE);
3590                                   }
3591                                   SendToProgram("playother\n", &first);
3592                                   firstMove = FALSE;
3593                                 } else {
3594                                   firstMove = TRUE;
3595                                 }
3596                             }
3597                         }
3598                     }
3599 #endif
3600                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3601                         /* Moves came from oldmoves or moves command
3602                            while we weren't doing anything else.
3603                            */
3604                         currentMove = forwardMostMove;
3605                         ClearHighlights();/*!!could figure this out*/
3606                         flipView = appData.flipView;
3607                         DrawPosition(TRUE, boards[currentMove]);
3608                         DisplayBothClocks();
3609                         snprintf(str, MSG_SIZ, "%s %s %s",
3610                                 gameInfo.white, _("vs."),  gameInfo.black);
3611                         DisplayTitle(str);
3612                         gameMode = IcsIdle;
3613                     } else {
3614                         /* Moves were history of an active game */
3615                         if (gameInfo.resultDetails != NULL) {
3616                             free(gameInfo.resultDetails);
3617                             gameInfo.resultDetails = NULL;
3618                         }
3619                     }
3620                     HistorySet(parseList, backwardMostMove,
3621                                forwardMostMove, currentMove-1);
3622                     DisplayMove(currentMove - 1);
3623                     if (started == STARTED_MOVES) next_out = i;
3624                     started = STARTED_NONE;
3625                     ics_getting_history = H_FALSE;
3626                     break;
3627
3628                   case STARTED_OBSERVE:
3629                     started = STARTED_NONE;
3630                     SendToICS(ics_prefix);
3631                     SendToICS("refresh\n");
3632                     break;
3633
3634                   default:
3635                     break;
3636                 }
3637                 if(bookHit) { // [HGM] book: simulate book reply
3638                     static char bookMove[MSG_SIZ]; // a bit generous?
3639
3640                     programStats.nodes = programStats.depth = programStats.time =
3641                     programStats.score = programStats.got_only_move = 0;
3642                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3643
3644                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3645                     strcat(bookMove, bookHit);
3646                     HandleMachineMove(bookMove, &first);
3647                 }
3648                 continue;
3649             }
3650
3651             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3652                  started == STARTED_HOLDINGS ||
3653                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3654                 /* Accumulate characters in move list or board */
3655                 parse[parse_pos++] = buf[i];
3656             }
3657
3658             /* Start of game messages.  Mostly we detect start of game
3659                when the first board image arrives.  On some versions
3660                of the ICS, though, we need to do a "refresh" after starting
3661                to observe in order to get the current board right away. */
3662             if (looking_at(buf, &i, "Adding game * to observation list")) {
3663                 started = STARTED_OBSERVE;
3664                 continue;
3665             }
3666
3667             /* Handle auto-observe */
3668             if (appData.autoObserve &&
3669                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3670                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3671                 char *player;
3672                 /* Choose the player that was highlighted, if any. */
3673                 if (star_match[0][0] == '\033' ||
3674                     star_match[1][0] != '\033') {
3675                     player = star_match[0];
3676                 } else {
3677                     player = star_match[2];
3678                 }
3679                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3680                         ics_prefix, StripHighlightAndTitle(player));
3681                 SendToICS(str);
3682
3683                 /* Save ratings from notify string */
3684                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3685                 player1Rating = string_to_rating(star_match[1]);
3686                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3687                 player2Rating = string_to_rating(star_match[3]);
3688
3689                 if (appData.debugMode)
3690                   fprintf(debugFP,
3691                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3692                           player1Name, player1Rating,
3693                           player2Name, player2Rating);
3694
3695                 continue;
3696             }
3697
3698             /* Deal with automatic examine mode after a game,
3699                and with IcsObserving -> IcsExamining transition */
3700             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3701                 looking_at(buf, &i, "has made you an examiner of game *")) {
3702
3703                 int gamenum = atoi(star_match[0]);
3704                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3705                     gamenum == ics_gamenum) {
3706                     /* We were already playing or observing this game;
3707                        no need to refetch history */
3708                     gameMode = IcsExamining;
3709                     if (pausing) {
3710                         pauseExamForwardMostMove = forwardMostMove;
3711                     } else if (currentMove < forwardMostMove) {
3712                         ForwardInner(forwardMostMove);
3713                     }
3714                 } else {
3715                     /* I don't think this case really can happen */
3716                     SendToICS(ics_prefix);
3717                     SendToICS("refresh\n");
3718                 }
3719                 continue;
3720             }
3721
3722             /* Error messages */
3723 //          if (ics_user_moved) {
3724             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3725                 if (looking_at(buf, &i, "Illegal move") ||
3726                     looking_at(buf, &i, "Not a legal move") ||
3727                     looking_at(buf, &i, "Your king is in check") ||
3728                     looking_at(buf, &i, "It isn't your turn") ||
3729                     looking_at(buf, &i, "It is not your move")) {
3730                     /* Illegal move */
3731                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3732                         currentMove = forwardMostMove-1;
3733                         DisplayMove(currentMove - 1); /* before DMError */
3734                         DrawPosition(FALSE, boards[currentMove]);
3735                         SwitchClocks(forwardMostMove-1); // [HGM] race
3736                         DisplayBothClocks();
3737                     }
3738                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3739                     ics_user_moved = 0;
3740                     continue;
3741                 }
3742             }
3743
3744             if (looking_at(buf, &i, "still have time") ||
3745                 looking_at(buf, &i, "not out of time") ||
3746                 looking_at(buf, &i, "either player is out of time") ||
3747                 looking_at(buf, &i, "has timeseal; checking")) {
3748                 /* We must have called his flag a little too soon */
3749                 whiteFlag = blackFlag = FALSE;
3750                 continue;
3751             }
3752
3753             if (looking_at(buf, &i, "added * seconds to") ||
3754                 looking_at(buf, &i, "seconds were added to")) {
3755                 /* Update the clocks */
3756                 SendToICS(ics_prefix);
3757                 SendToICS("refresh\n");
3758                 continue;
3759             }
3760
3761             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3762                 ics_clock_paused = TRUE;
3763                 StopClocks();
3764                 continue;
3765             }
3766
3767             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3768                 ics_clock_paused = FALSE;
3769                 StartClocks();
3770                 continue;
3771             }
3772
3773             /* Grab player ratings from the Creating: message.
3774                Note we have to check for the special case when
3775                the ICS inserts things like [white] or [black]. */
3776             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3777                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3778                 /* star_matches:
3779                    0    player 1 name (not necessarily white)
3780                    1    player 1 rating
3781                    2    empty, white, or black (IGNORED)
3782                    3    player 2 name (not necessarily black)
3783                    4    player 2 rating
3784
3785                    The names/ratings are sorted out when the game
3786                    actually starts (below).
3787                 */
3788                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3789                 player1Rating = string_to_rating(star_match[1]);
3790                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3791                 player2Rating = string_to_rating(star_match[4]);
3792
3793                 if (appData.debugMode)
3794                   fprintf(debugFP,
3795                           "Ratings from 'Creating:' %s %d, %s %d\n",
3796                           player1Name, player1Rating,
3797                           player2Name, player2Rating);
3798
3799                 continue;
3800             }
3801
3802             /* Improved generic start/end-of-game messages */
3803             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3804                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3805                 /* If tkind == 0: */
3806                 /* star_match[0] is the game number */
3807                 /*           [1] is the white player's name */
3808                 /*           [2] is the black player's name */
3809                 /* For end-of-game: */
3810                 /*           [3] is the reason for the game end */
3811                 /*           [4] is a PGN end game-token, preceded by " " */
3812                 /* For start-of-game: */
3813                 /*           [3] begins with "Creating" or "Continuing" */
3814                 /*           [4] is " *" or empty (don't care). */
3815                 int gamenum = atoi(star_match[0]);
3816                 char *whitename, *blackname, *why, *endtoken;
3817                 ChessMove endtype = EndOfFile;
3818
3819                 if (tkind == 0) {
3820                   whitename = star_match[1];
3821                   blackname = star_match[2];
3822                   why = star_match[3];
3823                   endtoken = star_match[4];
3824                 } else {
3825                   whitename = star_match[1];
3826                   blackname = star_match[3];
3827                   why = star_match[5];
3828                   endtoken = star_match[6];
3829                 }
3830
3831                 /* Game start messages */
3832                 if (strncmp(why, "Creating ", 9) == 0 ||
3833                     strncmp(why, "Continuing ", 11) == 0) {
3834                     gs_gamenum = gamenum;
3835                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3836                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3837                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3838 #if ZIPPY
3839                     if (appData.zippyPlay) {
3840                         ZippyGameStart(whitename, blackname);
3841                     }
3842 #endif /*ZIPPY*/
3843                     partnerBoardValid = FALSE; // [HGM] bughouse
3844                     continue;
3845                 }
3846
3847                 /* Game end messages */
3848                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3849                     ics_gamenum != gamenum) {
3850                     continue;
3851                 }
3852                 while (endtoken[0] == ' ') endtoken++;
3853                 switch (endtoken[0]) {
3854                   case '*':
3855                   default:
3856                     endtype = GameUnfinished;
3857                     break;
3858                   case '0':
3859                     endtype = BlackWins;
3860                     break;
3861                   case '1':
3862                     if (endtoken[1] == '/')
3863                       endtype = GameIsDrawn;
3864                     else
3865                       endtype = WhiteWins;
3866                     break;
3867                 }
3868                 GameEnds(endtype, why, GE_ICS);
3869 #if ZIPPY
3870                 if (appData.zippyPlay && first.initDone) {
3871                     ZippyGameEnd(endtype, why);
3872                     if (first.pr == NoProc) {
3873                       /* Start the next process early so that we'll
3874                          be ready for the next challenge */
3875                       StartChessProgram(&first);
3876                     }
3877                     /* Send "new" early, in case this command takes
3878                        a long time to finish, so that we'll be ready
3879                        for the next challenge. */
3880                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3881                     Reset(TRUE, TRUE);
3882                 }
3883 #endif /*ZIPPY*/
3884                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3885                 continue;
3886             }
3887
3888             if (looking_at(buf, &i, "Removing game * from observation") ||
3889                 looking_at(buf, &i, "no longer observing game *") ||
3890                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3891                 if (gameMode == IcsObserving &&
3892                     atoi(star_match[0]) == ics_gamenum)
3893                   {
3894                       /* icsEngineAnalyze */
3895                       if (appData.icsEngineAnalyze) {
3896                             ExitAnalyzeMode();
3897                             ModeHighlight();
3898                       }
3899                       StopClocks();
3900                       gameMode = IcsIdle;
3901                       ics_gamenum = -1;
3902                       ics_user_moved = FALSE;
3903                   }
3904                 continue;
3905             }
3906
3907             if (looking_at(buf, &i, "no longer examining game *")) {
3908                 if (gameMode == IcsExamining &&
3909                     atoi(star_match[0]) == ics_gamenum)
3910                   {
3911                       gameMode = IcsIdle;
3912                       ics_gamenum = -1;
3913                       ics_user_moved = FALSE;
3914                   }
3915                 continue;
3916             }
3917
3918             /* Advance leftover_start past any newlines we find,
3919                so only partial lines can get reparsed */
3920             if (looking_at(buf, &i, "\n")) {
3921                 prevColor = curColor;
3922                 if (curColor != ColorNormal) {
3923                     if (oldi > next_out) {
3924                         SendToPlayer(&buf[next_out], oldi - next_out);
3925                         next_out = oldi;
3926                     }
3927                     Colorize(ColorNormal, FALSE);
3928                     curColor = ColorNormal;
3929                 }
3930                 if (started == STARTED_BOARD) {
3931                     started = STARTED_NONE;
3932                     parse[parse_pos] = NULLCHAR;
3933                     ParseBoard12(parse);
3934                     ics_user_moved = 0;
3935
3936                     /* Send premove here */
3937                     if (appData.premove) {
3938                       char str[MSG_SIZ];
3939                       if (currentMove == 0 &&
3940                           gameMode == IcsPlayingWhite &&
3941                           appData.premoveWhite) {
3942                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3943                         if (appData.debugMode)
3944                           fprintf(debugFP, "Sending premove:\n");
3945                         SendToICS(str);
3946                       } else if (currentMove == 1 &&
3947                                  gameMode == IcsPlayingBlack &&
3948                                  appData.premoveBlack) {
3949                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3950                         if (appData.debugMode)
3951                           fprintf(debugFP, "Sending premove:\n");
3952                         SendToICS(str);
3953                       } else if (gotPremove) {
3954                         gotPremove = 0;
3955                         ClearPremoveHighlights();
3956                         if (appData.debugMode)
3957                           fprintf(debugFP, "Sending premove:\n");
3958                           UserMoveEvent(premoveFromX, premoveFromY,
3959                                         premoveToX, premoveToY,
3960                                         premovePromoChar);
3961                       }
3962                     }
3963
3964                     /* Usually suppress following prompt */
3965                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3966                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3967                         if (looking_at(buf, &i, "*% ")) {
3968                             savingComment = FALSE;
3969                             suppressKibitz = 0;
3970                         }
3971                     }
3972                     next_out = i;
3973                 } else if (started == STARTED_HOLDINGS) {
3974                     int gamenum;
3975                     char new_piece[MSG_SIZ];
3976                     started = STARTED_NONE;
3977                     parse[parse_pos] = NULLCHAR;
3978                     if (appData.debugMode)
3979                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3980                                                         parse, currentMove);
3981                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3982                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3983                         if (gameInfo.variant == VariantNormal) {
3984                           /* [HGM] We seem to switch variant during a game!
3985                            * Presumably no holdings were displayed, so we have
3986                            * to move the position two files to the right to
3987                            * create room for them!
3988                            */
3989                           VariantClass newVariant;
3990                           switch(gameInfo.boardWidth) { // base guess on board width
3991                                 case 9:  newVariant = VariantShogi; break;
3992                                 case 10: newVariant = VariantGreat; break;
3993                                 default: newVariant = VariantCrazyhouse; break;
3994                           }
3995                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3996                           /* Get a move list just to see the header, which
3997                              will tell us whether this is really bug or zh */
3998                           if (ics_getting_history == H_FALSE) {
3999                             ics_getting_history = H_REQUESTED;
4000                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4001                             SendToICS(str);
4002                           }
4003                         }
4004                         new_piece[0] = NULLCHAR;
4005                         sscanf(parse, "game %d white [%s black [%s <- %s",
4006                                &gamenum, white_holding, black_holding,
4007                                new_piece);
4008                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4009                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4010                         /* [HGM] copy holdings to board holdings area */
4011                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4012                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4013                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4014 #if ZIPPY
4015                         if (appData.zippyPlay && first.initDone) {
4016                             ZippyHoldings(white_holding, black_holding,
4017                                           new_piece);
4018                         }
4019 #endif /*ZIPPY*/
4020                         if (tinyLayout || smallLayout) {
4021                             char wh[16], bh[16];
4022                             PackHolding(wh, white_holding);
4023                             PackHolding(bh, black_holding);
4024                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4025                                     gameInfo.white, gameInfo.black);
4026                         } else {
4027                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4028                                     gameInfo.white, white_holding, _("vs."),
4029                                     gameInfo.black, black_holding);
4030                         }
4031                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4032                         DrawPosition(FALSE, boards[currentMove]);
4033                         DisplayTitle(str);
4034                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4035                         sscanf(parse, "game %d white [%s black [%s <- %s",
4036                                &gamenum, white_holding, black_holding,
4037                                new_piece);
4038                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4039                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4040                         /* [HGM] copy holdings to partner-board holdings area */
4041                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4042                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4043                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4044                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4045                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4046                       }
4047                     }
4048                     /* Suppress following prompt */
4049                     if (looking_at(buf, &i, "*% ")) {
4050                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4051                         savingComment = FALSE;
4052                         suppressKibitz = 0;
4053                     }
4054                     next_out = i;
4055                 }
4056                 continue;
4057             }
4058
4059             i++;                /* skip unparsed character and loop back */
4060         }
4061
4062         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4063 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4064 //          SendToPlayer(&buf[next_out], i - next_out);
4065             started != STARTED_HOLDINGS && leftover_start > next_out) {
4066             SendToPlayer(&buf[next_out], leftover_start - next_out);
4067             next_out = i;
4068         }
4069
4070         leftover_len = buf_len - leftover_start;
4071         /* if buffer ends with something we couldn't parse,
4072            reparse it after appending the next read */
4073
4074     } else if (count == 0) {
4075         RemoveInputSource(isr);
4076         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4077     } else {
4078         DisplayFatalError(_("Error reading from ICS"), error, 1);
4079     }
4080 }
4081
4082
4083 /* Board style 12 looks like this:
4084
4085    <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
4086
4087  * The "<12> " is stripped before it gets to this routine.  The two
4088  * trailing 0's (flip state and clock ticking) are later addition, and
4089  * some chess servers may not have them, or may have only the first.
4090  * Additional trailing fields may be added in the future.
4091  */
4092
4093 #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"
4094
4095 #define RELATION_OBSERVING_PLAYED    0
4096 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4097 #define RELATION_PLAYING_MYMOVE      1
4098 #define RELATION_PLAYING_NOTMYMOVE  -1
4099 #define RELATION_EXAMINING           2
4100 #define RELATION_ISOLATED_BOARD     -3
4101 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4102
4103 void
4104 ParseBoard12 (char *string)
4105 {
4106     GameMode newGameMode;
4107     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4108     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4109     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4110     char to_play, board_chars[200];
4111     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4112     char black[32], white[32];
4113     Board board;
4114     int prevMove = currentMove;
4115     int ticking = 2;
4116     ChessMove moveType;
4117     int fromX, fromY, toX, toY;
4118     char promoChar;
4119     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4120     char *bookHit = NULL; // [HGM] book
4121     Boolean weird = FALSE, reqFlag = FALSE;
4122
4123     fromX = fromY = toX = toY = -1;
4124
4125     newGame = FALSE;
4126
4127     if (appData.debugMode)
4128       fprintf(debugFP, _("Parsing board: %s\n"), string);
4129
4130     move_str[0] = NULLCHAR;
4131     elapsed_time[0] = NULLCHAR;
4132     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4133         int  i = 0, j;
4134         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4135             if(string[i] == ' ') { ranks++; files = 0; }
4136             else files++;
4137             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4138             i++;
4139         }
4140         for(j = 0; j <i; j++) board_chars[j] = string[j];
4141         board_chars[i] = '\0';
4142         string += i + 1;
4143     }
4144     n = sscanf(string, PATTERN, &to_play, &double_push,
4145                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4146                &gamenum, white, black, &relation, &basetime, &increment,
4147                &white_stren, &black_stren, &white_time, &black_time,
4148                &moveNum, str, elapsed_time, move_str, &ics_flip,
4149                &ticking);
4150
4151     if (n < 21) {
4152         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4153         DisplayError(str, 0);
4154         return;
4155     }
4156
4157     /* Convert the move number to internal form */
4158     moveNum = (moveNum - 1) * 2;
4159     if (to_play == 'B') moveNum++;
4160     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4161       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4162                         0, 1);
4163       return;
4164     }
4165
4166     switch (relation) {
4167       case RELATION_OBSERVING_PLAYED:
4168       case RELATION_OBSERVING_STATIC:
4169         if (gamenum == -1) {
4170             /* Old ICC buglet */
4171             relation = RELATION_OBSERVING_STATIC;
4172         }
4173         newGameMode = IcsObserving;
4174         break;
4175       case RELATION_PLAYING_MYMOVE:
4176       case RELATION_PLAYING_NOTMYMOVE:
4177         newGameMode =
4178           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4179             IcsPlayingWhite : IcsPlayingBlack;
4180         break;
4181       case RELATION_EXAMINING:
4182         newGameMode = IcsExamining;
4183         break;
4184       case RELATION_ISOLATED_BOARD:
4185       default:
4186         /* Just display this board.  If user was doing something else,
4187            we will forget about it until the next board comes. */
4188         newGameMode = IcsIdle;
4189         break;
4190       case RELATION_STARTING_POSITION:
4191         newGameMode = gameMode;
4192         break;
4193     }
4194
4195     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4196          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4197       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4198       char *toSqr;
4199       for (k = 0; k < ranks; k++) {
4200         for (j = 0; j < files; j++)
4201           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4202         if(gameInfo.holdingsWidth > 1) {
4203              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4204              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4205         }
4206       }
4207       CopyBoard(partnerBoard, board);
4208       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4209         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4210         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4211       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4212       if(toSqr = strchr(str, '-')) {
4213         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4214         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4215       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4216       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4217       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4218       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4219       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4220       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4221                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4222       DisplayMessage(partnerStatus, "");
4223         partnerBoardValid = TRUE;
4224       return;
4225     }
4226
4227     /* Modify behavior for initial board display on move listing
4228        of wild games.
4229        */
4230     switch (ics_getting_history) {
4231       case H_FALSE:
4232       case H_REQUESTED:
4233         break;
4234       case H_GOT_REQ_HEADER:
4235       case H_GOT_UNREQ_HEADER:
4236         /* This is the initial position of the current game */
4237         gamenum = ics_gamenum;
4238         moveNum = 0;            /* old ICS bug workaround */
4239         if (to_play == 'B') {
4240           startedFromSetupPosition = TRUE;
4241           blackPlaysFirst = TRUE;
4242           moveNum = 1;
4243           if (forwardMostMove == 0) forwardMostMove = 1;
4244           if (backwardMostMove == 0) backwardMostMove = 1;
4245           if (currentMove == 0) currentMove = 1;
4246         }
4247         newGameMode = gameMode;
4248         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4249         break;
4250       case H_GOT_UNWANTED_HEADER:
4251         /* This is an initial board that we don't want */
4252         return;
4253       case H_GETTING_MOVES:
4254         /* Should not happen */
4255         DisplayError(_("Error gathering move list: extra board"), 0);
4256         ics_getting_history = H_FALSE;
4257         return;
4258     }
4259
4260    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4261                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4262      /* [HGM] We seem to have switched variant unexpectedly
4263       * Try to guess new variant from board size
4264       */
4265           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4266           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4267           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4268           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4269           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4270           if(!weird) newVariant = VariantNormal;
4271           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4272           /* Get a move list just to see the header, which
4273              will tell us whether this is really bug or zh */
4274           if (ics_getting_history == H_FALSE) {
4275             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4276             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4277             SendToICS(str);
4278           }
4279     }
4280
4281     /* Take action if this is the first board of a new game, or of a
4282        different game than is currently being displayed.  */
4283     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4284         relation == RELATION_ISOLATED_BOARD) {
4285
4286         /* Forget the old game and get the history (if any) of the new one */
4287         if (gameMode != BeginningOfGame) {
4288           Reset(TRUE, TRUE);
4289         }
4290         newGame = TRUE;
4291         if (appData.autoRaiseBoard) BoardToTop();
4292         prevMove = -3;
4293         if (gamenum == -1) {
4294             newGameMode = IcsIdle;
4295         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4296                    appData.getMoveList && !reqFlag) {
4297             /* Need to get game history */
4298             ics_getting_history = H_REQUESTED;
4299             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4300             SendToICS(str);
4301         }
4302
4303         /* Initially flip the board to have black on the bottom if playing
4304            black or if the ICS flip flag is set, but let the user change
4305            it with the Flip View button. */
4306         flipView = appData.autoFlipView ?
4307           (newGameMode == IcsPlayingBlack) || ics_flip :
4308           appData.flipView;
4309
4310         /* Done with values from previous mode; copy in new ones */
4311         gameMode = newGameMode;
4312         ModeHighlight();
4313         ics_gamenum = gamenum;
4314         if (gamenum == gs_gamenum) {
4315             int klen = strlen(gs_kind);
4316             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4317             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4318             gameInfo.event = StrSave(str);
4319         } else {
4320             gameInfo.event = StrSave("ICS game");
4321         }
4322         gameInfo.site = StrSave(appData.icsHost);
4323         gameInfo.date = PGNDate();
4324         gameInfo.round = StrSave("-");
4325         gameInfo.white = StrSave(white);
4326         gameInfo.black = StrSave(black);
4327         timeControl = basetime * 60 * 1000;
4328         timeControl_2 = 0;
4329         timeIncrement = increment * 1000;
4330         movesPerSession = 0;
4331         gameInfo.timeControl = TimeControlTagValue();
4332         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4333   if (appData.debugMode) {
4334     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4335     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4336     setbuf(debugFP, NULL);
4337   }
4338
4339         gameInfo.outOfBook = NULL;
4340
4341         /* Do we have the ratings? */
4342         if (strcmp(player1Name, white) == 0 &&
4343             strcmp(player2Name, black) == 0) {
4344             if (appData.debugMode)
4345               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4346                       player1Rating, player2Rating);
4347             gameInfo.whiteRating = player1Rating;
4348             gameInfo.blackRating = player2Rating;
4349         } else if (strcmp(player2Name, white) == 0 &&
4350                    strcmp(player1Name, black) == 0) {
4351             if (appData.debugMode)
4352               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4353                       player2Rating, player1Rating);
4354             gameInfo.whiteRating = player2Rating;
4355             gameInfo.blackRating = player1Rating;
4356         }
4357         player1Name[0] = player2Name[0] = NULLCHAR;
4358
4359         /* Silence shouts if requested */
4360         if (appData.quietPlay &&
4361             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4362             SendToICS(ics_prefix);
4363             SendToICS("set shout 0\n");
4364         }
4365     }
4366
4367     /* Deal with midgame name changes */
4368     if (!newGame) {
4369         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4370             if (gameInfo.white) free(gameInfo.white);
4371             gameInfo.white = StrSave(white);
4372         }
4373         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4374             if (gameInfo.black) free(gameInfo.black);
4375             gameInfo.black = StrSave(black);
4376         }
4377     }
4378
4379     /* Throw away game result if anything actually changes in examine mode */
4380     if (gameMode == IcsExamining && !newGame) {
4381         gameInfo.result = GameUnfinished;
4382         if (gameInfo.resultDetails != NULL) {
4383             free(gameInfo.resultDetails);
4384             gameInfo.resultDetails = NULL;
4385         }
4386     }
4387
4388     /* In pausing && IcsExamining mode, we ignore boards coming
4389        in if they are in a different variation than we are. */
4390     if (pauseExamInvalid) return;
4391     if (pausing && gameMode == IcsExamining) {
4392         if (moveNum <= pauseExamForwardMostMove) {
4393             pauseExamInvalid = TRUE;
4394             forwardMostMove = pauseExamForwardMostMove;
4395             return;
4396         }
4397     }
4398
4399   if (appData.debugMode) {
4400     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4401   }
4402     /* Parse the board */
4403     for (k = 0; k < ranks; k++) {
4404       for (j = 0; j < files; j++)
4405         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4406       if(gameInfo.holdingsWidth > 1) {
4407            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4408            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4409       }
4410     }
4411     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4412       board[5][BOARD_RGHT+1] = WhiteAngel;
4413       board[6][BOARD_RGHT+1] = WhiteMarshall;
4414       board[1][0] = BlackMarshall;
4415       board[2][0] = BlackAngel;
4416       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4417     }
4418     CopyBoard(boards[moveNum], board);
4419     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4420     if (moveNum == 0) {
4421         startedFromSetupPosition =
4422           !CompareBoards(board, initialPosition);
4423         if(startedFromSetupPosition)
4424             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4425     }
4426
4427     /* [HGM] Set castling rights. Take the outermost Rooks,
4428        to make it also work for FRC opening positions. Note that board12
4429        is really defective for later FRC positions, as it has no way to
4430        indicate which Rook can castle if they are on the same side of King.
4431        For the initial position we grant rights to the outermost Rooks,
4432        and remember thos rights, and we then copy them on positions
4433        later in an FRC game. This means WB might not recognize castlings with
4434        Rooks that have moved back to their original position as illegal,
4435        but in ICS mode that is not its job anyway.
4436     */
4437     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4438     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4439
4440         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4441             if(board[0][i] == WhiteRook) j = i;
4442         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4443         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4444             if(board[0][i] == WhiteRook) j = i;
4445         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4446         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4447             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4448         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4449         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4450             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4451         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4452
4453         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4454         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4455         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4456             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4457         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4458             if(board[BOARD_HEIGHT-1][k] == bKing)
4459                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4460         if(gameInfo.variant == VariantTwoKings) {
4461             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4462             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4463             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4464         }
4465     } else { int r;
4466         r = boards[moveNum][CASTLING][0] = initialRights[0];
4467         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4468         r = boards[moveNum][CASTLING][1] = initialRights[1];
4469         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4470         r = boards[moveNum][CASTLING][3] = initialRights[3];
4471         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4472         r = boards[moveNum][CASTLING][4] = initialRights[4];
4473         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4474         /* wildcastle kludge: always assume King has rights */
4475         r = boards[moveNum][CASTLING][2] = initialRights[2];
4476         r = boards[moveNum][CASTLING][5] = initialRights[5];
4477     }
4478     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4479     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4480
4481
4482     if (ics_getting_history == H_GOT_REQ_HEADER ||
4483         ics_getting_history == H_GOT_UNREQ_HEADER) {
4484         /* This was an initial position from a move list, not
4485            the current position */
4486         return;
4487     }
4488
4489     /* Update currentMove and known move number limits */
4490     newMove = newGame || moveNum > forwardMostMove;
4491
4492     if (newGame) {
4493         forwardMostMove = backwardMostMove = currentMove = moveNum;
4494         if (gameMode == IcsExamining && moveNum == 0) {
4495           /* Workaround for ICS limitation: we are not told the wild
4496              type when starting to examine a game.  But if we ask for
4497              the move list, the move list header will tell us */
4498             ics_getting_history = H_REQUESTED;
4499             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4500             SendToICS(str);
4501         }
4502     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4503                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4504 #if ZIPPY
4505         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4506         /* [HGM] applied this also to an engine that is silently watching        */
4507         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4508             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4509             gameInfo.variant == currentlyInitializedVariant) {
4510           takeback = forwardMostMove - moveNum;
4511           for (i = 0; i < takeback; i++) {
4512             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4513             SendToProgram("undo\n", &first);
4514           }
4515         }
4516 #endif
4517
4518         forwardMostMove = moveNum;
4519         if (!pausing || currentMove > forwardMostMove)
4520           currentMove = forwardMostMove;
4521     } else {
4522         /* New part of history that is not contiguous with old part */
4523         if (pausing && gameMode == IcsExamining) {
4524             pauseExamInvalid = TRUE;
4525             forwardMostMove = pauseExamForwardMostMove;
4526             return;
4527         }
4528         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4529 #if ZIPPY
4530             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4531                 // [HGM] when we will receive the move list we now request, it will be
4532                 // fed to the engine from the first move on. So if the engine is not
4533                 // in the initial position now, bring it there.
4534                 InitChessProgram(&first, 0);
4535             }
4536 #endif
4537             ics_getting_history = H_REQUESTED;
4538             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4539             SendToICS(str);
4540         }
4541         forwardMostMove = backwardMostMove = currentMove = moveNum;
4542     }
4543
4544     /* Update the clocks */
4545     if (strchr(elapsed_time, '.')) {
4546       /* Time is in ms */
4547       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4548       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4549     } else {
4550       /* Time is in seconds */
4551       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4552       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4553     }
4554
4555
4556 #if ZIPPY
4557     if (appData.zippyPlay && newGame &&
4558         gameMode != IcsObserving && gameMode != IcsIdle &&
4559         gameMode != IcsExamining)
4560       ZippyFirstBoard(moveNum, basetime, increment);
4561 #endif
4562
4563     /* Put the move on the move list, first converting
4564        to canonical algebraic form. */
4565     if (moveNum > 0) {
4566   if (appData.debugMode) {
4567     if (appData.debugMode) { int f = forwardMostMove;
4568         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4569                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4570                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4571     }
4572     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4573     fprintf(debugFP, "moveNum = %d\n", moveNum);
4574     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4575     setbuf(debugFP, NULL);
4576   }
4577         if (moveNum <= backwardMostMove) {
4578             /* We don't know what the board looked like before
4579                this move.  Punt. */
4580           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4581             strcat(parseList[moveNum - 1], " ");
4582             strcat(parseList[moveNum - 1], elapsed_time);
4583             moveList[moveNum - 1][0] = NULLCHAR;
4584         } else if (strcmp(move_str, "none") == 0) {
4585             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4586             /* Again, we don't know what the board looked like;
4587                this is really the start of the game. */
4588             parseList[moveNum - 1][0] = NULLCHAR;
4589             moveList[moveNum - 1][0] = NULLCHAR;
4590             backwardMostMove = moveNum;
4591             startedFromSetupPosition = TRUE;
4592             fromX = fromY = toX = toY = -1;
4593         } else {
4594           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4595           //                 So we parse the long-algebraic move string in stead of the SAN move
4596           int valid; char buf[MSG_SIZ], *prom;
4597
4598           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4599                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4600           // str looks something like "Q/a1-a2"; kill the slash
4601           if(str[1] == '/')
4602             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4603           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4604           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4605                 strcat(buf, prom); // long move lacks promo specification!
4606           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4607                 if(appData.debugMode)
4608                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4609                 safeStrCpy(move_str, buf, MSG_SIZ);
4610           }
4611           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4612                                 &fromX, &fromY, &toX, &toY, &promoChar)
4613                || ParseOneMove(buf, moveNum - 1, &moveType,
4614                                 &fromX, &fromY, &toX, &toY, &promoChar);
4615           // end of long SAN patch
4616           if (valid) {
4617             (void) CoordsToAlgebraic(boards[moveNum - 1],
4618                                      PosFlags(moveNum - 1),
4619                                      fromY, fromX, toY, toX, promoChar,
4620                                      parseList[moveNum-1]);
4621             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4622               case MT_NONE:
4623               case MT_STALEMATE:
4624               default:
4625                 break;
4626               case MT_CHECK:
4627                 if(gameInfo.variant != VariantShogi)
4628                     strcat(parseList[moveNum - 1], "+");
4629                 break;
4630               case MT_CHECKMATE:
4631               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4632                 strcat(parseList[moveNum - 1], "#");
4633                 break;
4634             }
4635             strcat(parseList[moveNum - 1], " ");
4636             strcat(parseList[moveNum - 1], elapsed_time);
4637             /* currentMoveString is set as a side-effect of ParseOneMove */
4638             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4639             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4640             strcat(moveList[moveNum - 1], "\n");
4641
4642             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4643                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4644               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4645                 ChessSquare old, new = boards[moveNum][k][j];
4646                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4647                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4648                   if(old == new) continue;
4649                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4650                   else if(new == WhiteWazir || new == BlackWazir) {
4651                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4652                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4653                       else boards[moveNum][k][j] = old; // preserve type of Gold
4654                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4655                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4656               }
4657           } else {
4658             /* Move from ICS was illegal!?  Punt. */
4659             if (appData.debugMode) {
4660               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4661               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4662             }
4663             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4664             strcat(parseList[moveNum - 1], " ");
4665             strcat(parseList[moveNum - 1], elapsed_time);
4666             moveList[moveNum - 1][0] = NULLCHAR;
4667             fromX = fromY = toX = toY = -1;
4668           }
4669         }
4670   if (appData.debugMode) {
4671     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4672     setbuf(debugFP, NULL);
4673   }
4674
4675 #if ZIPPY
4676         /* Send move to chess program (BEFORE animating it). */
4677         if (appData.zippyPlay && !newGame && newMove &&
4678            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4679
4680             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4681                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4682                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4683                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4684                             move_str);
4685                     DisplayError(str, 0);
4686                 } else {
4687                     if (first.sendTime) {
4688                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4689                     }
4690                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4691                     if (firstMove && !bookHit) {
4692                         firstMove = FALSE;
4693                         if (first.useColors) {
4694                           SendToProgram(gameMode == IcsPlayingWhite ?
4695                                         "white\ngo\n" :
4696                                         "black\ngo\n", &first);
4697                         } else {
4698                           SendToProgram("go\n", &first);
4699                         }
4700                         first.maybeThinking = TRUE;
4701                     }
4702                 }
4703             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4704               if (moveList[moveNum - 1][0] == NULLCHAR) {
4705                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4706                 DisplayError(str, 0);
4707               } else {
4708                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4709                 SendMoveToProgram(moveNum - 1, &first);
4710               }
4711             }
4712         }
4713 #endif
4714     }
4715
4716     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4717         /* If move comes from a remote source, animate it.  If it
4718            isn't remote, it will have already been animated. */
4719         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4720             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4721         }
4722         if (!pausing && appData.highlightLastMove) {
4723             SetHighlights(fromX, fromY, toX, toY);
4724         }
4725     }
4726
4727     /* Start the clocks */
4728     whiteFlag = blackFlag = FALSE;
4729     appData.clockMode = !(basetime == 0 && increment == 0);
4730     if (ticking == 0) {
4731       ics_clock_paused = TRUE;
4732       StopClocks();
4733     } else if (ticking == 1) {
4734       ics_clock_paused = FALSE;
4735     }
4736     if (gameMode == IcsIdle ||
4737         relation == RELATION_OBSERVING_STATIC ||
4738         relation == RELATION_EXAMINING ||
4739         ics_clock_paused)
4740       DisplayBothClocks();
4741     else
4742       StartClocks();
4743
4744     /* Display opponents and material strengths */
4745     if (gameInfo.variant != VariantBughouse &&
4746         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4747         if (tinyLayout || smallLayout) {
4748             if(gameInfo.variant == VariantNormal)
4749               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4750                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4751                     basetime, increment);
4752             else
4753               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4754                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4755                     basetime, increment, (int) gameInfo.variant);
4756         } else {
4757             if(gameInfo.variant == VariantNormal)
4758               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4759                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4760                     basetime, increment);
4761             else
4762               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4763                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4764                     basetime, increment, VariantName(gameInfo.variant));
4765         }
4766         DisplayTitle(str);
4767   if (appData.debugMode) {
4768     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4769   }
4770     }
4771
4772
4773     /* Display the board */
4774     if (!pausing && !appData.noGUI) {
4775
4776       if (appData.premove)
4777           if (!gotPremove ||
4778              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4779              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4780               ClearPremoveHighlights();
4781
4782       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4783         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4784       DrawPosition(j, boards[currentMove]);
4785
4786       DisplayMove(moveNum - 1);
4787       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4788             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4789               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4790         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4791       }
4792     }
4793
4794     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4795 #if ZIPPY
4796     if(bookHit) { // [HGM] book: simulate book reply
4797         static char bookMove[MSG_SIZ]; // a bit generous?
4798
4799         programStats.nodes = programStats.depth = programStats.time =
4800         programStats.score = programStats.got_only_move = 0;
4801         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4802
4803         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4804         strcat(bookMove, bookHit);
4805         HandleMachineMove(bookMove, &first);
4806     }
4807 #endif
4808 }
4809
4810 void
4811 GetMoveListEvent ()
4812 {
4813     char buf[MSG_SIZ];
4814     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4815         ics_getting_history = H_REQUESTED;
4816         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4817         SendToICS(buf);
4818     }
4819 }
4820
4821 void
4822 AnalysisPeriodicEvent (int force)
4823 {
4824     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4825          && !force) || !appData.periodicUpdates)
4826       return;
4827
4828     /* Send . command to Crafty to collect stats */
4829     SendToProgram(".\n", &first);
4830
4831     /* Don't send another until we get a response (this makes
4832        us stop sending to old Crafty's which don't understand
4833        the "." command (sending illegal cmds resets node count & time,
4834        which looks bad)) */
4835     programStats.ok_to_send = 0;
4836 }
4837
4838 void
4839 ics_update_width (int new_width)
4840 {
4841         ics_printf("set width %d\n", new_width);
4842 }
4843
4844 void
4845 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4846 {
4847     char buf[MSG_SIZ];
4848
4849     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4850         // null move in variant where engine does not understand it (for analysis purposes)
4851         SendBoard(cps, moveNum + 1); // send position after move in stead.
4852         return;
4853     }
4854     if (cps->useUsermove) {
4855       SendToProgram("usermove ", cps);
4856     }
4857     if (cps->useSAN) {
4858       char *space;
4859       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4860         int len = space - parseList[moveNum];
4861         memcpy(buf, parseList[moveNum], len);
4862         buf[len++] = '\n';
4863         buf[len] = NULLCHAR;
4864       } else {
4865         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4866       }
4867       SendToProgram(buf, cps);
4868     } else {
4869       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4870         AlphaRank(moveList[moveNum], 4);
4871         SendToProgram(moveList[moveNum], cps);
4872         AlphaRank(moveList[moveNum], 4); // and back
4873       } else
4874       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4875        * the engine. It would be nice to have a better way to identify castle
4876        * moves here. */
4877       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4878                                                                          && cps->useOOCastle) {
4879         int fromX = moveList[moveNum][0] - AAA;
4880         int fromY = moveList[moveNum][1] - ONE;
4881         int toX = moveList[moveNum][2] - AAA;
4882         int toY = moveList[moveNum][3] - ONE;
4883         if((boards[moveNum][fromY][fromX] == WhiteKing
4884             && boards[moveNum][toY][toX] == WhiteRook)
4885            || (boards[moveNum][fromY][fromX] == BlackKing
4886                && boards[moveNum][toY][toX] == BlackRook)) {
4887           if(toX > fromX) SendToProgram("O-O\n", cps);
4888           else SendToProgram("O-O-O\n", cps);
4889         }
4890         else SendToProgram(moveList[moveNum], cps);
4891       } else
4892       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4893         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4894           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4895           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4896                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4897         } else
4898           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4899                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4900         SendToProgram(buf, cps);
4901       }
4902       else SendToProgram(moveList[moveNum], cps);
4903       /* End of additions by Tord */
4904     }
4905
4906     /* [HGM] setting up the opening has brought engine in force mode! */
4907     /*       Send 'go' if we are in a mode where machine should play. */
4908     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4909         (gameMode == TwoMachinesPlay   ||
4910 #if ZIPPY
4911          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4912 #endif
4913          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4914         SendToProgram("go\n", cps);
4915   if (appData.debugMode) {
4916     fprintf(debugFP, "(extra)\n");
4917   }
4918     }
4919     setboardSpoiledMachineBlack = 0;
4920 }
4921
4922 void
4923 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4924 {
4925     char user_move[MSG_SIZ];
4926     char suffix[4];
4927
4928     if(gameInfo.variant == VariantSChess && promoChar) {
4929         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4930         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4931     } else suffix[0] = NULLCHAR;
4932
4933     switch (moveType) {
4934       default:
4935         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4936                 (int)moveType, fromX, fromY, toX, toY);
4937         DisplayError(user_move + strlen("say "), 0);
4938         break;
4939       case WhiteKingSideCastle:
4940       case BlackKingSideCastle:
4941       case WhiteQueenSideCastleWild:
4942       case BlackQueenSideCastleWild:
4943       /* PUSH Fabien */
4944       case WhiteHSideCastleFR:
4945       case BlackHSideCastleFR:
4946       /* POP Fabien */
4947         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4948         break;
4949       case WhiteQueenSideCastle:
4950       case BlackQueenSideCastle:
4951       case WhiteKingSideCastleWild:
4952       case BlackKingSideCastleWild:
4953       /* PUSH Fabien */
4954       case WhiteASideCastleFR:
4955       case BlackASideCastleFR:
4956       /* POP Fabien */
4957         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
4958         break;
4959       case WhiteNonPromotion:
4960       case BlackNonPromotion:
4961         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4962         break;
4963       case WhitePromotion:
4964       case BlackPromotion:
4965         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4966           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4967                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4968                 PieceToChar(WhiteFerz));
4969         else if(gameInfo.variant == VariantGreat)
4970           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4971                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4972                 PieceToChar(WhiteMan));
4973         else
4974           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4975                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4976                 promoChar);
4977         break;
4978       case WhiteDrop:
4979       case BlackDrop:
4980       drop:
4981         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4982                  ToUpper(PieceToChar((ChessSquare) fromX)),
4983                  AAA + toX, ONE + toY);
4984         break;
4985       case IllegalMove:  /* could be a variant we don't quite understand */
4986         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4987       case NormalMove:
4988       case WhiteCapturesEnPassant:
4989       case BlackCapturesEnPassant:
4990         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4991                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4992         break;
4993     }
4994     SendToICS(user_move);
4995     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4996         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4997 }
4998
4999 void
5000 UploadGameEvent ()
5001 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5002     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5003     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5004     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5005       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5006       return;
5007     }
5008     if(gameMode != IcsExamining) { // is this ever not the case?
5009         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5010
5011         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5012           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5013         } else { // on FICS we must first go to general examine mode
5014           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5015         }
5016         if(gameInfo.variant != VariantNormal) {
5017             // try figure out wild number, as xboard names are not always valid on ICS
5018             for(i=1; i<=36; i++) {
5019               snprintf(buf, MSG_SIZ, "wild/%d", i);
5020                 if(StringToVariant(buf) == gameInfo.variant) break;
5021             }
5022             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5023             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5024             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5025         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5026         SendToICS(ics_prefix);
5027         SendToICS(buf);
5028         if(startedFromSetupPosition || backwardMostMove != 0) {
5029           fen = PositionToFEN(backwardMostMove, NULL);
5030           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5031             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5032             SendToICS(buf);
5033           } else { // FICS: everything has to set by separate bsetup commands
5034             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5035             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5036             SendToICS(buf);
5037             if(!WhiteOnMove(backwardMostMove)) {
5038                 SendToICS("bsetup tomove black\n");
5039             }
5040             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5041             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5042             SendToICS(buf);
5043             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5044             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5045             SendToICS(buf);
5046             i = boards[backwardMostMove][EP_STATUS];
5047             if(i >= 0) { // set e.p.
5048               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5049                 SendToICS(buf);
5050             }
5051             bsetup++;
5052           }
5053         }
5054       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5055             SendToICS("bsetup done\n"); // switch to normal examining.
5056     }
5057     for(i = backwardMostMove; i<last; i++) {
5058         char buf[20];
5059         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5060         SendToICS(buf);
5061     }
5062     SendToICS(ics_prefix);
5063     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5064 }
5065
5066 void
5067 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5068 {
5069     if (rf == DROP_RANK) {
5070       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5071       sprintf(move, "%c@%c%c\n",
5072                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5073     } else {
5074         if (promoChar == 'x' || promoChar == NULLCHAR) {
5075           sprintf(move, "%c%c%c%c\n",
5076                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5077         } else {
5078             sprintf(move, "%c%c%c%c%c\n",
5079                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5080         }
5081     }
5082 }
5083
5084 void
5085 ProcessICSInitScript (FILE *f)
5086 {
5087     char buf[MSG_SIZ];
5088
5089     while (fgets(buf, MSG_SIZ, f)) {
5090         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5091     }
5092
5093     fclose(f);
5094 }
5095
5096
5097 static int lastX, lastY, selectFlag, dragging;
5098
5099 void
5100 Sweep (int step)
5101 {
5102     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5103     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5104     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5105     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5106     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5107     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5108     do {
5109         promoSweep -= step;
5110         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5111         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5112         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5113         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5114         if(!step) step = -1;
5115     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5116             appData.testLegality && (promoSweep == king ||
5117             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5118     ChangeDragPiece(promoSweep);
5119 }
5120
5121 int
5122 PromoScroll (int x, int y)
5123 {
5124   int step = 0;
5125
5126   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5127   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5128   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5129   if(!step) return FALSE;
5130   lastX = x; lastY = y;
5131   if((promoSweep < BlackPawn) == flipView) step = -step;
5132   if(step > 0) selectFlag = 1;
5133   if(!selectFlag) Sweep(step);
5134   return FALSE;
5135 }
5136
5137 void
5138 NextPiece (int step)
5139 {
5140     ChessSquare piece = boards[currentMove][toY][toX];
5141     do {
5142         pieceSweep -= step;
5143         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5144         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5145         if(!step) step = -1;
5146     } while(PieceToChar(pieceSweep) == '.');
5147     boards[currentMove][toY][toX] = pieceSweep;
5148     DrawPosition(FALSE, boards[currentMove]);
5149     boards[currentMove][toY][toX] = piece;
5150 }
5151 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5152 void
5153 AlphaRank (char *move, int n)
5154 {
5155 //    char *p = move, c; int x, y;
5156
5157     if (appData.debugMode) {
5158         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5159     }
5160
5161     if(move[1]=='*' &&
5162        move[2]>='0' && move[2]<='9' &&
5163        move[3]>='a' && move[3]<='x'    ) {
5164         move[1] = '@';
5165         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5166         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5167     } else
5168     if(move[0]>='0' && move[0]<='9' &&
5169        move[1]>='a' && move[1]<='x' &&
5170        move[2]>='0' && move[2]<='9' &&
5171        move[3]>='a' && move[3]<='x'    ) {
5172         /* input move, Shogi -> normal */
5173         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5174         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5175         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5176         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5177     } else
5178     if(move[1]=='@' &&
5179        move[3]>='0' && move[3]<='9' &&
5180        move[2]>='a' && move[2]<='x'    ) {
5181         move[1] = '*';
5182         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5183         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5184     } else
5185     if(
5186        move[0]>='a' && move[0]<='x' &&
5187        move[3]>='0' && move[3]<='9' &&
5188        move[2]>='a' && move[2]<='x'    ) {
5189          /* output move, normal -> Shogi */
5190         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5191         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5192         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5193         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5194         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5195     }
5196     if (appData.debugMode) {
5197         fprintf(debugFP, "   out = '%s'\n", move);
5198     }
5199 }
5200
5201 char yy_textstr[8000];
5202
5203 /* Parser for moves from gnuchess, ICS, or user typein box */
5204 Boolean
5205 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5206 {
5207     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5208
5209     switch (*moveType) {
5210       case WhitePromotion:
5211       case BlackPromotion:
5212       case WhiteNonPromotion:
5213       case BlackNonPromotion:
5214       case NormalMove:
5215       case WhiteCapturesEnPassant:
5216       case BlackCapturesEnPassant:
5217       case WhiteKingSideCastle:
5218       case WhiteQueenSideCastle:
5219       case BlackKingSideCastle:
5220       case BlackQueenSideCastle:
5221       case WhiteKingSideCastleWild:
5222       case WhiteQueenSideCastleWild:
5223       case BlackKingSideCastleWild:
5224       case BlackQueenSideCastleWild:
5225       /* Code added by Tord: */
5226       case WhiteHSideCastleFR:
5227       case WhiteASideCastleFR:
5228       case BlackHSideCastleFR:
5229       case BlackASideCastleFR:
5230       /* End of code added by Tord */
5231       case IllegalMove:         /* bug or odd chess variant */
5232         *fromX = currentMoveString[0] - AAA;
5233         *fromY = currentMoveString[1] - ONE;
5234         *toX = currentMoveString[2] - AAA;
5235         *toY = currentMoveString[3] - ONE;
5236         *promoChar = currentMoveString[4];
5237         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5238             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5239     if (appData.debugMode) {
5240         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5241     }
5242             *fromX = *fromY = *toX = *toY = 0;
5243             return FALSE;
5244         }
5245         if (appData.testLegality) {
5246           return (*moveType != IllegalMove);
5247         } else {
5248           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5249                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5250         }
5251
5252       case WhiteDrop:
5253       case BlackDrop:
5254         *fromX = *moveType == WhiteDrop ?
5255           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5256           (int) CharToPiece(ToLower(currentMoveString[0]));
5257         *fromY = DROP_RANK;
5258         *toX = currentMoveString[2] - AAA;
5259         *toY = currentMoveString[3] - ONE;
5260         *promoChar = NULLCHAR;
5261         return TRUE;
5262
5263       case AmbiguousMove:
5264       case ImpossibleMove:
5265       case EndOfFile:
5266       case ElapsedTime:
5267       case Comment:
5268       case PGNTag:
5269       case NAG:
5270       case WhiteWins:
5271       case BlackWins:
5272       case GameIsDrawn:
5273       default:
5274     if (appData.debugMode) {
5275         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5276     }
5277         /* bug? */
5278         *fromX = *fromY = *toX = *toY = 0;
5279         *promoChar = NULLCHAR;
5280         return FALSE;
5281     }
5282 }
5283
5284 Boolean pushed = FALSE;
5285 char *lastParseAttempt;
5286
5287 void
5288 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5289 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5290   int fromX, fromY, toX, toY; char promoChar;
5291   ChessMove moveType;
5292   Boolean valid;
5293   int nr = 0;
5294
5295   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5296     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5297     pushed = TRUE;
5298   }
5299   endPV = forwardMostMove;
5300   do {
5301     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5302     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5303     lastParseAttempt = pv;
5304     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5305 if(appData.debugMode){
5306 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);
5307 }
5308     if(!valid && nr == 0 &&
5309        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5310         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5311         // Hande case where played move is different from leading PV move
5312         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5313         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5314         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5315         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5316           endPV += 2; // if position different, keep this
5317           moveList[endPV-1][0] = fromX + AAA;
5318           moveList[endPV-1][1] = fromY + ONE;
5319           moveList[endPV-1][2] = toX + AAA;
5320           moveList[endPV-1][3] = toY + ONE;
5321           parseList[endPV-1][0] = NULLCHAR;
5322           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5323         }
5324       }
5325     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5326     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5327     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5328     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5329         valid++; // allow comments in PV
5330         continue;
5331     }
5332     nr++;
5333     if(endPV+1 > framePtr) break; // no space, truncate
5334     if(!valid) break;
5335     endPV++;
5336     CopyBoard(boards[endPV], boards[endPV-1]);
5337     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5338     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5339     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5340     CoordsToAlgebraic(boards[endPV - 1],
5341                              PosFlags(endPV - 1),
5342                              fromY, fromX, toY, toX, promoChar,
5343                              parseList[endPV - 1]);
5344   } while(valid);
5345   if(atEnd == 2) return; // used hidden, for PV conversion
5346   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5347   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5348   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5349                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5350   DrawPosition(TRUE, boards[currentMove]);
5351 }
5352
5353 int
5354 MultiPV (ChessProgramState *cps)
5355 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5356         int i;
5357         for(i=0; i<cps->nrOptions; i++)
5358             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5359                 return i;
5360         return -1;
5361 }
5362
5363 Boolean
5364 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5365 {
5366         int startPV, multi, lineStart, origIndex = index;
5367         char *p, buf2[MSG_SIZ];
5368
5369         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5370         lastX = x; lastY = y;
5371         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5372         lineStart = startPV = index;
5373         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5374         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5375         index = startPV;
5376         do{ while(buf[index] && buf[index] != '\n') index++;
5377         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5378         buf[index] = 0;
5379         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5380                 int n = first.option[multi].value;
5381                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5382                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5383                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5384                 first.option[multi].value = n;
5385                 *start = *end = 0;
5386                 return FALSE;
5387         }
5388         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5389         *start = startPV; *end = index-1;
5390         return TRUE;
5391 }
5392
5393 char *
5394 PvToSAN (char *pv)
5395 {
5396         static char buf[10*MSG_SIZ];
5397         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5398         *buf = NULLCHAR;
5399         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5400         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5401         for(i = forwardMostMove; i<endPV; i++){
5402             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5403             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5404             k += strlen(buf+k);
5405         }
5406         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5407         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5408         endPV = savedEnd;
5409         return buf;
5410 }
5411
5412 Boolean
5413 LoadPV (int x, int y)
5414 { // called on right mouse click to load PV
5415   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5416   lastX = x; lastY = y;
5417   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5418   return TRUE;
5419 }
5420
5421 void
5422 UnLoadPV ()
5423 {
5424   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5425   if(endPV < 0) return;
5426   endPV = -1;
5427   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5428         Boolean saveAnimate = appData.animate;
5429         if(pushed) {
5430             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5431                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5432             } else storedGames--; // abandon shelved tail of original game
5433         }
5434         pushed = FALSE;
5435         forwardMostMove = currentMove;
5436         currentMove = oldFMM;
5437         appData.animate = FALSE;
5438         ToNrEvent(forwardMostMove);
5439         appData.animate = saveAnimate;
5440   }
5441   currentMove = forwardMostMove;
5442   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5443   ClearPremoveHighlights();
5444   DrawPosition(TRUE, boards[currentMove]);
5445 }
5446
5447 void
5448 MovePV (int x, int y, int h)
5449 { // step through PV based on mouse coordinates (called on mouse move)
5450   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5451
5452   // we must somehow check if right button is still down (might be released off board!)
5453   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5454   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5455   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5456   if(!step) return;
5457   lastX = x; lastY = y;
5458
5459   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5460   if(endPV < 0) return;
5461   if(y < margin) step = 1; else
5462   if(y > h - margin) step = -1;
5463   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5464   currentMove += step;
5465   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5466   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5467                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5468   DrawPosition(FALSE, boards[currentMove]);
5469 }
5470
5471
5472 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5473 // All positions will have equal probability, but the current method will not provide a unique
5474 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5475 #define DARK 1
5476 #define LITE 2
5477 #define ANY 3
5478
5479 int squaresLeft[4];
5480 int piecesLeft[(int)BlackPawn];
5481 int seed, nrOfShuffles;
5482
5483 void
5484 GetPositionNumber ()
5485 {       // sets global variable seed
5486         int i;
5487
5488         seed = appData.defaultFrcPosition;
5489         if(seed < 0) { // randomize based on time for negative FRC position numbers
5490                 for(i=0; i<50; i++) seed += random();
5491                 seed = random() ^ random() >> 8 ^ random() << 8;
5492                 if(seed<0) seed = -seed;
5493         }
5494 }
5495
5496 int
5497 put (Board board, int pieceType, int rank, int n, int shade)
5498 // put the piece on the (n-1)-th empty squares of the given shade
5499 {
5500         int i;
5501
5502         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5503                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5504                         board[rank][i] = (ChessSquare) pieceType;
5505                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5506                         squaresLeft[ANY]--;
5507                         piecesLeft[pieceType]--;
5508                         return i;
5509                 }
5510         }
5511         return -1;
5512 }
5513
5514
5515 void
5516 AddOnePiece (Board board, int pieceType, int rank, int shade)
5517 // calculate where the next piece goes, (any empty square), and put it there
5518 {
5519         int i;
5520
5521         i = seed % squaresLeft[shade];
5522         nrOfShuffles *= squaresLeft[shade];
5523         seed /= squaresLeft[shade];
5524         put(board, pieceType, rank, i, shade);
5525 }
5526
5527 void
5528 AddTwoPieces (Board board, int pieceType, int rank)
5529 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5530 {
5531         int i, n=squaresLeft[ANY], j=n-1, k;
5532
5533         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5534         i = seed % k;  // pick one
5535         nrOfShuffles *= k;
5536         seed /= k;
5537         while(i >= j) i -= j--;
5538         j = n - 1 - j; i += j;
5539         put(board, pieceType, rank, j, ANY);
5540         put(board, pieceType, rank, i, ANY);
5541 }
5542
5543 void
5544 SetUpShuffle (Board board, int number)
5545 {
5546         int i, p, first=1;
5547
5548         GetPositionNumber(); nrOfShuffles = 1;
5549
5550         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5551         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5552         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5553
5554         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5555
5556         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5557             p = (int) board[0][i];
5558             if(p < (int) BlackPawn) piecesLeft[p] ++;
5559             board[0][i] = EmptySquare;
5560         }
5561
5562         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5563             // shuffles restricted to allow normal castling put KRR first
5564             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5565                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5566             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5567                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5568             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5569                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5570             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5571                 put(board, WhiteRook, 0, 0, ANY);
5572             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5573         }
5574
5575         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5576             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5577             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5578                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5579                 while(piecesLeft[p] >= 2) {
5580                     AddOnePiece(board, p, 0, LITE);
5581                     AddOnePiece(board, p, 0, DARK);
5582                 }
5583                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5584             }
5585
5586         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5587             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5588             // but we leave King and Rooks for last, to possibly obey FRC restriction
5589             if(p == (int)WhiteRook) continue;
5590             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5591             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5592         }
5593
5594         // now everything is placed, except perhaps King (Unicorn) and Rooks
5595
5596         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5597             // Last King gets castling rights
5598             while(piecesLeft[(int)WhiteUnicorn]) {
5599                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5600                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5601             }
5602
5603             while(piecesLeft[(int)WhiteKing]) {
5604                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5605                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5606             }
5607
5608
5609         } else {
5610             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5611             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5612         }
5613
5614         // Only Rooks can be left; simply place them all
5615         while(piecesLeft[(int)WhiteRook]) {
5616                 i = put(board, WhiteRook, 0, 0, ANY);
5617                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5618                         if(first) {
5619                                 first=0;
5620                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5621                         }
5622                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5623                 }
5624         }
5625         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5626             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5627         }
5628
5629         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5630 }
5631
5632 int
5633 SetCharTable (char *table, const char * map)
5634 /* [HGM] moved here from winboard.c because of its general usefulness */
5635 /*       Basically a safe strcpy that uses the last character as King */
5636 {
5637     int result = FALSE; int NrPieces;
5638
5639     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5640                     && NrPieces >= 12 && !(NrPieces&1)) {
5641         int i; /* [HGM] Accept even length from 12 to 34 */
5642
5643         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5644         for( i=0; i<NrPieces/2-1; i++ ) {
5645             table[i] = map[i];
5646             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5647         }
5648         table[(int) WhiteKing]  = map[NrPieces/2-1];
5649         table[(int) BlackKing]  = map[NrPieces-1];
5650
5651         result = TRUE;
5652     }
5653
5654     return result;
5655 }
5656
5657 void
5658 Prelude (Board board)
5659 {       // [HGM] superchess: random selection of exo-pieces
5660         int i, j, k; ChessSquare p;
5661         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5662
5663         GetPositionNumber(); // use FRC position number
5664
5665         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5666             SetCharTable(pieceToChar, appData.pieceToCharTable);
5667             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5668                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5669         }
5670
5671         j = seed%4;                 seed /= 4;
5672         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5673         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5674         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5675         j = seed%3 + (seed%3 >= j); seed /= 3;
5676         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5677         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5678         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5679         j = seed%3;                 seed /= 3;
5680         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5681         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5682         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5683         j = seed%2 + (seed%2 >= j); seed /= 2;
5684         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5685         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5686         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5687         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5688         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5689         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5690         put(board, exoPieces[0],    0, 0, ANY);
5691         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5692 }
5693
5694 void
5695 InitPosition (int redraw)
5696 {
5697     ChessSquare (* pieces)[BOARD_FILES];
5698     int i, j, pawnRow, overrule,
5699     oldx = gameInfo.boardWidth,
5700     oldy = gameInfo.boardHeight,
5701     oldh = gameInfo.holdingsWidth;
5702     static int oldv;
5703
5704     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5705
5706     /* [AS] Initialize pv info list [HGM] and game status */
5707     {
5708         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5709             pvInfoList[i].depth = 0;
5710             boards[i][EP_STATUS] = EP_NONE;
5711             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5712         }
5713
5714         initialRulePlies = 0; /* 50-move counter start */
5715
5716         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5717         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5718     }
5719
5720
5721     /* [HGM] logic here is completely changed. In stead of full positions */
5722     /* the initialized data only consist of the two backranks. The switch */
5723     /* selects which one we will use, which is than copied to the Board   */
5724     /* initialPosition, which for the rest is initialized by Pawns and    */
5725     /* empty squares. This initial position is then copied to boards[0],  */
5726     /* possibly after shuffling, so that it remains available.            */
5727
5728     gameInfo.holdingsWidth = 0; /* default board sizes */
5729     gameInfo.boardWidth    = 8;
5730     gameInfo.boardHeight   = 8;
5731     gameInfo.holdingsSize  = 0;
5732     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5733     for(i=0; i<BOARD_FILES-2; i++)
5734       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5735     initialPosition[EP_STATUS] = EP_NONE;
5736     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5737     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5738          SetCharTable(pieceNickName, appData.pieceNickNames);
5739     else SetCharTable(pieceNickName, "............");
5740     pieces = FIDEArray;
5741
5742     switch (gameInfo.variant) {
5743     case VariantFischeRandom:
5744       shuffleOpenings = TRUE;
5745     default:
5746       break;
5747     case VariantShatranj:
5748       pieces = ShatranjArray;
5749       nrCastlingRights = 0;
5750       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5751       break;
5752     case VariantMakruk:
5753       pieces = makrukArray;
5754       nrCastlingRights = 0;
5755       startedFromSetupPosition = TRUE;
5756       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5757       break;
5758     case VariantTwoKings:
5759       pieces = twoKingsArray;
5760       break;
5761     case VariantGrand:
5762       pieces = GrandArray;
5763       nrCastlingRights = 0;
5764       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5765       gameInfo.boardWidth = 10;
5766       gameInfo.boardHeight = 10;
5767       gameInfo.holdingsSize = 7;
5768       break;
5769     case VariantCapaRandom:
5770       shuffleOpenings = TRUE;
5771     case VariantCapablanca:
5772       pieces = CapablancaArray;
5773       gameInfo.boardWidth = 10;
5774       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5775       break;
5776     case VariantGothic:
5777       pieces = GothicArray;
5778       gameInfo.boardWidth = 10;
5779       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5780       break;
5781     case VariantSChess:
5782       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5783       gameInfo.holdingsSize = 7;
5784       break;
5785     case VariantJanus:
5786       pieces = JanusArray;
5787       gameInfo.boardWidth = 10;
5788       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5789       nrCastlingRights = 6;
5790         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5791         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5792         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5793         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5794         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5795         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5796       break;
5797     case VariantFalcon:
5798       pieces = FalconArray;
5799       gameInfo.boardWidth = 10;
5800       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5801       break;
5802     case VariantXiangqi:
5803       pieces = XiangqiArray;
5804       gameInfo.boardWidth  = 9;
5805       gameInfo.boardHeight = 10;
5806       nrCastlingRights = 0;
5807       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5808       break;
5809     case VariantShogi:
5810       pieces = ShogiArray;
5811       gameInfo.boardWidth  = 9;
5812       gameInfo.boardHeight = 9;
5813       gameInfo.holdingsSize = 7;
5814       nrCastlingRights = 0;
5815       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5816       break;
5817     case VariantCourier:
5818       pieces = CourierArray;
5819       gameInfo.boardWidth  = 12;
5820       nrCastlingRights = 0;
5821       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5822       break;
5823     case VariantKnightmate:
5824       pieces = KnightmateArray;
5825       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5826       break;
5827     case VariantSpartan:
5828       pieces = SpartanArray;
5829       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5830       break;
5831     case VariantFairy:
5832       pieces = fairyArray;
5833       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5834       break;
5835     case VariantGreat:
5836       pieces = GreatArray;
5837       gameInfo.boardWidth = 10;
5838       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5839       gameInfo.holdingsSize = 8;
5840       break;
5841     case VariantSuper:
5842       pieces = FIDEArray;
5843       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5844       gameInfo.holdingsSize = 8;
5845       startedFromSetupPosition = TRUE;
5846       break;
5847     case VariantCrazyhouse:
5848     case VariantBughouse:
5849       pieces = FIDEArray;
5850       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5851       gameInfo.holdingsSize = 5;
5852       break;
5853     case VariantWildCastle:
5854       pieces = FIDEArray;
5855       /* !!?shuffle with kings guaranteed to be on d or e file */
5856       shuffleOpenings = 1;
5857       break;
5858     case VariantNoCastle:
5859       pieces = FIDEArray;
5860       nrCastlingRights = 0;
5861       /* !!?unconstrained back-rank shuffle */
5862       shuffleOpenings = 1;
5863       break;
5864     }
5865
5866     overrule = 0;
5867     if(appData.NrFiles >= 0) {
5868         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5869         gameInfo.boardWidth = appData.NrFiles;
5870     }
5871     if(appData.NrRanks >= 0) {
5872         gameInfo.boardHeight = appData.NrRanks;
5873     }
5874     if(appData.holdingsSize >= 0) {
5875         i = appData.holdingsSize;
5876         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5877         gameInfo.holdingsSize = i;
5878     }
5879     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5880     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5881         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5882
5883     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5884     if(pawnRow < 1) pawnRow = 1;
5885     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5886
5887     /* User pieceToChar list overrules defaults */
5888     if(appData.pieceToCharTable != NULL)
5889         SetCharTable(pieceToChar, appData.pieceToCharTable);
5890
5891     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5892
5893         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5894             s = (ChessSquare) 0; /* account holding counts in guard band */
5895         for( i=0; i<BOARD_HEIGHT; i++ )
5896             initialPosition[i][j] = s;
5897
5898         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5899         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5900         initialPosition[pawnRow][j] = WhitePawn;
5901         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5902         if(gameInfo.variant == VariantXiangqi) {
5903             if(j&1) {
5904                 initialPosition[pawnRow][j] =
5905                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5906                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5907                    initialPosition[2][j] = WhiteCannon;
5908                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5909                 }
5910             }
5911         }
5912         if(gameInfo.variant == VariantGrand) {
5913             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5914                initialPosition[0][j] = WhiteRook;
5915                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5916             }
5917         }
5918         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5919     }
5920     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5921
5922             j=BOARD_LEFT+1;
5923             initialPosition[1][j] = WhiteBishop;
5924             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5925             j=BOARD_RGHT-2;
5926             initialPosition[1][j] = WhiteRook;
5927             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5928     }
5929
5930     if( nrCastlingRights == -1) {
5931         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5932         /*       This sets default castling rights from none to normal corners   */
5933         /* Variants with other castling rights must set them themselves above    */
5934         nrCastlingRights = 6;
5935
5936         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5937         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5938         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5939         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5940         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5941         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5942      }
5943
5944      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5945      if(gameInfo.variant == VariantGreat) { // promotion commoners
5946         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5947         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5948         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5949         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5950      }
5951      if( gameInfo.variant == VariantSChess ) {
5952       initialPosition[1][0] = BlackMarshall;
5953       initialPosition[2][0] = BlackAngel;
5954       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5955       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5956       initialPosition[1][1] = initialPosition[2][1] = 
5957       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5958      }
5959   if (appData.debugMode) {
5960     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5961   }
5962     if(shuffleOpenings) {
5963         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5964         startedFromSetupPosition = TRUE;
5965     }
5966     if(startedFromPositionFile) {
5967       /* [HGM] loadPos: use PositionFile for every new game */
5968       CopyBoard(initialPosition, filePosition);
5969       for(i=0; i<nrCastlingRights; i++)
5970           initialRights[i] = filePosition[CASTLING][i];
5971       startedFromSetupPosition = TRUE;
5972     }
5973
5974     CopyBoard(boards[0], initialPosition);
5975
5976     if(oldx != gameInfo.boardWidth ||
5977        oldy != gameInfo.boardHeight ||
5978        oldv != gameInfo.variant ||
5979        oldh != gameInfo.holdingsWidth
5980                                          )
5981             InitDrawingSizes(-2 ,0);
5982
5983     oldv = gameInfo.variant;
5984     if (redraw)
5985       DrawPosition(TRUE, boards[currentMove]);
5986 }
5987
5988 void
5989 SendBoard (ChessProgramState *cps, int moveNum)
5990 {
5991     char message[MSG_SIZ];
5992
5993     if (cps->useSetboard) {
5994       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5995       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5996       SendToProgram(message, cps);
5997       free(fen);
5998
5999     } else {
6000       ChessSquare *bp;
6001       int i, j, left=0, right=BOARD_WIDTH;
6002       /* Kludge to set black to move, avoiding the troublesome and now
6003        * deprecated "black" command.
6004        */
6005       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6006         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6007
6008       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6009
6010       SendToProgram("edit\n", cps);
6011       SendToProgram("#\n", cps);
6012       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6013         bp = &boards[moveNum][i][left];
6014         for (j = left; j < right; j++, bp++) {
6015           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6016           if ((int) *bp < (int) BlackPawn) {
6017             if(j == BOARD_RGHT+1)
6018                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6019             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6020             if(message[0] == '+' || message[0] == '~') {
6021               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6022                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6023                         AAA + j, ONE + i);
6024             }
6025             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6026                 message[1] = BOARD_RGHT   - 1 - j + '1';
6027                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6028             }
6029             SendToProgram(message, cps);
6030           }
6031         }
6032       }
6033
6034       SendToProgram("c\n", cps);
6035       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6036         bp = &boards[moveNum][i][left];
6037         for (j = left; j < right; j++, bp++) {
6038           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6039           if (((int) *bp != (int) EmptySquare)
6040               && ((int) *bp >= (int) BlackPawn)) {
6041             if(j == BOARD_LEFT-2)
6042                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6043             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6044                     AAA + j, ONE + i);
6045             if(message[0] == '+' || message[0] == '~') {
6046               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6047                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6048                         AAA + j, ONE + i);
6049             }
6050             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6051                 message[1] = BOARD_RGHT   - 1 - j + '1';
6052                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6053             }
6054             SendToProgram(message, cps);
6055           }
6056         }
6057       }
6058
6059       SendToProgram(".\n", cps);
6060     }
6061     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6062 }
6063
6064 ChessSquare
6065 DefaultPromoChoice (int white)
6066 {
6067     ChessSquare result;
6068     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6069         result = WhiteFerz; // no choice
6070     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6071         result= WhiteKing; // in Suicide Q is the last thing we want
6072     else if(gameInfo.variant == VariantSpartan)
6073         result = white ? WhiteQueen : WhiteAngel;
6074     else result = WhiteQueen;
6075     if(!white) result = WHITE_TO_BLACK result;
6076     return result;
6077 }
6078
6079 static int autoQueen; // [HGM] oneclick
6080
6081 int
6082 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6083 {
6084     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6085     /* [HGM] add Shogi promotions */
6086     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6087     ChessSquare piece;
6088     ChessMove moveType;
6089     Boolean premove;
6090
6091     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6092     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6093
6094     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6095       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6096         return FALSE;
6097
6098     piece = boards[currentMove][fromY][fromX];
6099     if(gameInfo.variant == VariantShogi) {
6100         promotionZoneSize = BOARD_HEIGHT/3;
6101         highestPromotingPiece = (int)WhiteFerz;
6102     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6103         promotionZoneSize = 3;
6104     }
6105
6106     // Treat Lance as Pawn when it is not representing Amazon
6107     if(gameInfo.variant != VariantSuper) {
6108         if(piece == WhiteLance) piece = WhitePawn; else
6109         if(piece == BlackLance) piece = BlackPawn;
6110     }
6111
6112     // next weed out all moves that do not touch the promotion zone at all
6113     if((int)piece >= BlackPawn) {
6114         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6115              return FALSE;
6116         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6117     } else {
6118         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6119            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6120     }
6121
6122     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6123
6124     // weed out mandatory Shogi promotions
6125     if(gameInfo.variant == VariantShogi) {
6126         if(piece >= BlackPawn) {
6127             if(toY == 0 && piece == BlackPawn ||
6128                toY == 0 && piece == BlackQueen ||
6129                toY <= 1 && piece == BlackKnight) {
6130                 *promoChoice = '+';
6131                 return FALSE;
6132             }
6133         } else {
6134             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6135                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6136                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6137                 *promoChoice = '+';
6138                 return FALSE;
6139             }
6140         }
6141     }
6142
6143     // weed out obviously illegal Pawn moves
6144     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6145         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6146         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6147         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6148         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6149         // note we are not allowed to test for valid (non-)capture, due to premove
6150     }
6151
6152     // we either have a choice what to promote to, or (in Shogi) whether to promote
6153     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6154         *promoChoice = PieceToChar(BlackFerz);  // no choice
6155         return FALSE;
6156     }
6157     // no sense asking what we must promote to if it is going to explode...
6158     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6159         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6160         return FALSE;
6161     }
6162     // give caller the default choice even if we will not make it
6163     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6164     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6165     if(        sweepSelect && gameInfo.variant != VariantGreat
6166                            && gameInfo.variant != VariantGrand
6167                            && gameInfo.variant != VariantSuper) return FALSE;
6168     if(autoQueen) return FALSE; // predetermined
6169
6170     // suppress promotion popup on illegal moves that are not premoves
6171     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6172               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6173     if(appData.testLegality && !premove) {
6174         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6175                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6176         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6177             return FALSE;
6178     }
6179
6180     return TRUE;
6181 }
6182
6183 int
6184 InPalace (int row, int column)
6185 {   /* [HGM] for Xiangqi */
6186     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6187          column < (BOARD_WIDTH + 4)/2 &&
6188          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6189     return FALSE;
6190 }
6191
6192 int
6193 PieceForSquare (int x, int y)
6194 {
6195   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6196      return -1;
6197   else
6198      return boards[currentMove][y][x];
6199 }
6200
6201 int
6202 OKToStartUserMove (int x, int y)
6203 {
6204     ChessSquare from_piece;
6205     int white_piece;
6206
6207     if (matchMode) return FALSE;
6208     if (gameMode == EditPosition) return TRUE;
6209
6210     if (x >= 0 && y >= 0)
6211       from_piece = boards[currentMove][y][x];
6212     else
6213       from_piece = EmptySquare;
6214
6215     if (from_piece == EmptySquare) return FALSE;
6216
6217     white_piece = (int)from_piece >= (int)WhitePawn &&
6218       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6219
6220     switch (gameMode) {
6221       case AnalyzeFile:
6222       case TwoMachinesPlay:
6223       case EndOfGame:
6224         return FALSE;
6225
6226       case IcsObserving:
6227       case IcsIdle:
6228         return FALSE;
6229
6230       case MachinePlaysWhite:
6231       case IcsPlayingBlack:
6232         if (appData.zippyPlay) return FALSE;
6233         if (white_piece) {
6234             DisplayMoveError(_("You are playing Black"));
6235             return FALSE;
6236         }
6237         break;
6238
6239       case MachinePlaysBlack:
6240       case IcsPlayingWhite:
6241         if (appData.zippyPlay) return FALSE;
6242         if (!white_piece) {
6243             DisplayMoveError(_("You are playing White"));
6244             return FALSE;
6245         }
6246         break;
6247
6248       case PlayFromGameFile:
6249             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6250       case EditGame:
6251         if (!white_piece && WhiteOnMove(currentMove)) {
6252             DisplayMoveError(_("It is White's turn"));
6253             return FALSE;
6254         }
6255         if (white_piece && !WhiteOnMove(currentMove)) {
6256             DisplayMoveError(_("It is Black's turn"));
6257             return FALSE;
6258         }
6259         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6260             /* Editing correspondence game history */
6261             /* Could disallow this or prompt for confirmation */
6262             cmailOldMove = -1;
6263         }
6264         break;
6265
6266       case BeginningOfGame:
6267         if (appData.icsActive) return FALSE;
6268         if (!appData.noChessProgram) {
6269             if (!white_piece) {
6270                 DisplayMoveError(_("You are playing White"));
6271                 return FALSE;
6272             }
6273         }
6274         break;
6275
6276       case Training:
6277         if (!white_piece && WhiteOnMove(currentMove)) {
6278             DisplayMoveError(_("It is White's turn"));
6279             return FALSE;
6280         }
6281         if (white_piece && !WhiteOnMove(currentMove)) {
6282             DisplayMoveError(_("It is Black's turn"));
6283             return FALSE;
6284         }
6285         break;
6286
6287       default:
6288       case IcsExamining:
6289         break;
6290     }
6291     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6292         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6293         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6294         && gameMode != AnalyzeFile && gameMode != Training) {
6295         DisplayMoveError(_("Displayed position is not current"));
6296         return FALSE;
6297     }
6298     return TRUE;
6299 }
6300
6301 Boolean
6302 OnlyMove (int *x, int *y, Boolean captures) 
6303 {
6304     DisambiguateClosure cl;
6305     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6306     switch(gameMode) {
6307       case MachinePlaysBlack:
6308       case IcsPlayingWhite:
6309       case BeginningOfGame:
6310         if(!WhiteOnMove(currentMove)) return FALSE;
6311         break;
6312       case MachinePlaysWhite:
6313       case IcsPlayingBlack:
6314         if(WhiteOnMove(currentMove)) return FALSE;
6315         break;
6316       case EditGame:
6317         break;
6318       default:
6319         return FALSE;
6320     }
6321     cl.pieceIn = EmptySquare;
6322     cl.rfIn = *y;
6323     cl.ffIn = *x;
6324     cl.rtIn = -1;
6325     cl.ftIn = -1;
6326     cl.promoCharIn = NULLCHAR;
6327     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6328     if( cl.kind == NormalMove ||
6329         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6330         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6331         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6332       fromX = cl.ff;
6333       fromY = cl.rf;
6334       *x = cl.ft;
6335       *y = cl.rt;
6336       return TRUE;
6337     }
6338     if(cl.kind != ImpossibleMove) return FALSE;
6339     cl.pieceIn = EmptySquare;
6340     cl.rfIn = -1;
6341     cl.ffIn = -1;
6342     cl.rtIn = *y;
6343     cl.ftIn = *x;
6344     cl.promoCharIn = NULLCHAR;
6345     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6346     if( cl.kind == NormalMove ||
6347         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6348         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6349         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6350       fromX = cl.ff;
6351       fromY = cl.rf;
6352       *x = cl.ft;
6353       *y = cl.rt;
6354       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6355       return TRUE;
6356     }
6357     return FALSE;
6358 }
6359
6360 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6361 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6362 int lastLoadGameUseList = FALSE;
6363 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6364 ChessMove lastLoadGameStart = EndOfFile;
6365
6366 void
6367 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6368 {
6369     ChessMove moveType;
6370     ChessSquare pdown, pup;
6371
6372     /* Check if the user is playing in turn.  This is complicated because we
6373        let the user "pick up" a piece before it is his turn.  So the piece he
6374        tried to pick up may have been captured by the time he puts it down!
6375        Therefore we use the color the user is supposed to be playing in this
6376        test, not the color of the piece that is currently on the starting
6377        square---except in EditGame mode, where the user is playing both
6378        sides; fortunately there the capture race can't happen.  (It can
6379        now happen in IcsExamining mode, but that's just too bad.  The user
6380        will get a somewhat confusing message in that case.)
6381        */
6382
6383     switch (gameMode) {
6384       case AnalyzeFile:
6385       case TwoMachinesPlay:
6386       case EndOfGame:
6387       case IcsObserving:
6388       case IcsIdle:
6389         /* We switched into a game mode where moves are not accepted,
6390            perhaps while the mouse button was down. */
6391         return;
6392
6393       case MachinePlaysWhite:
6394         /* User is moving for Black */
6395         if (WhiteOnMove(currentMove)) {
6396             DisplayMoveError(_("It is White's turn"));
6397             return;
6398         }
6399         break;
6400
6401       case MachinePlaysBlack:
6402         /* User is moving for White */
6403         if (!WhiteOnMove(currentMove)) {
6404             DisplayMoveError(_("It is Black's turn"));
6405             return;
6406         }
6407         break;
6408
6409       case PlayFromGameFile:
6410             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6411       case EditGame:
6412       case IcsExamining:
6413       case BeginningOfGame:
6414       case AnalyzeMode:
6415       case Training:
6416         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6417         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6418             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6419             /* User is moving for Black */
6420             if (WhiteOnMove(currentMove)) {
6421                 DisplayMoveError(_("It is White's turn"));
6422                 return;
6423             }
6424         } else {
6425             /* User is moving for White */
6426             if (!WhiteOnMove(currentMove)) {
6427                 DisplayMoveError(_("It is Black's turn"));
6428                 return;
6429             }
6430         }
6431         break;
6432
6433       case IcsPlayingBlack:
6434         /* User is moving for Black */
6435         if (WhiteOnMove(currentMove)) {
6436             if (!appData.premove) {
6437                 DisplayMoveError(_("It is White's turn"));
6438             } else if (toX >= 0 && toY >= 0) {
6439                 premoveToX = toX;
6440                 premoveToY = toY;
6441                 premoveFromX = fromX;
6442                 premoveFromY = fromY;
6443                 premovePromoChar = promoChar;
6444                 gotPremove = 1;
6445                 if (appData.debugMode)
6446                     fprintf(debugFP, "Got premove: fromX %d,"
6447                             "fromY %d, toX %d, toY %d\n",
6448                             fromX, fromY, toX, toY);
6449             }
6450             return;
6451         }
6452         break;
6453
6454       case IcsPlayingWhite:
6455         /* User is moving for White */
6456         if (!WhiteOnMove(currentMove)) {
6457             if (!appData.premove) {
6458                 DisplayMoveError(_("It is Black's turn"));
6459             } else if (toX >= 0 && toY >= 0) {
6460                 premoveToX = toX;
6461                 premoveToY = toY;
6462                 premoveFromX = fromX;
6463                 premoveFromY = fromY;
6464                 premovePromoChar = promoChar;
6465                 gotPremove = 1;
6466                 if (appData.debugMode)
6467                     fprintf(debugFP, "Got premove: fromX %d,"
6468                             "fromY %d, toX %d, toY %d\n",
6469                             fromX, fromY, toX, toY);
6470             }
6471             return;
6472         }
6473         break;
6474
6475       default:
6476         break;
6477
6478       case EditPosition:
6479         /* EditPosition, empty square, or different color piece;
6480            click-click move is possible */
6481         if (toX == -2 || toY == -2) {
6482             boards[0][fromY][fromX] = EmptySquare;
6483             DrawPosition(FALSE, boards[currentMove]);
6484             return;
6485         } else if (toX >= 0 && toY >= 0) {
6486             boards[0][toY][toX] = boards[0][fromY][fromX];
6487             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6488                 if(boards[0][fromY][0] != EmptySquare) {
6489                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6490                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6491                 }
6492             } else
6493             if(fromX == BOARD_RGHT+1) {
6494                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6495                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6496                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6497                 }
6498             } else
6499             boards[0][fromY][fromX] = EmptySquare;
6500             DrawPosition(FALSE, boards[currentMove]);
6501             return;
6502         }
6503         return;
6504     }
6505
6506     if(toX < 0 || toY < 0) return;
6507     pdown = boards[currentMove][fromY][fromX];
6508     pup = boards[currentMove][toY][toX];
6509
6510     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6511     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6512          if( pup != EmptySquare ) return;
6513          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6514            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6515                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6516            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6517            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6518            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6519            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6520          fromY = DROP_RANK;
6521     }
6522
6523     /* [HGM] always test for legality, to get promotion info */
6524     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6525                                          fromY, fromX, toY, toX, promoChar);
6526
6527     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6528
6529     /* [HGM] but possibly ignore an IllegalMove result */
6530     if (appData.testLegality) {
6531         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6532             DisplayMoveError(_("Illegal move"));
6533             return;
6534         }
6535     }
6536
6537     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6538 }
6539
6540 /* Common tail of UserMoveEvent and DropMenuEvent */
6541 int
6542 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6543 {
6544     char *bookHit = 0;
6545
6546     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6547         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6548         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6549         if(WhiteOnMove(currentMove)) {
6550             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6551         } else {
6552             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6553         }
6554     }
6555
6556     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6557        move type in caller when we know the move is a legal promotion */
6558     if(moveType == NormalMove && promoChar)
6559         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6560
6561     /* [HGM] <popupFix> The following if has been moved here from
6562        UserMoveEvent(). Because it seemed to belong here (why not allow
6563        piece drops in training games?), and because it can only be
6564        performed after it is known to what we promote. */
6565     if (gameMode == Training) {
6566       /* compare the move played on the board to the next move in the
6567        * game. If they match, display the move and the opponent's response.
6568        * If they don't match, display an error message.
6569        */
6570       int saveAnimate;
6571       Board testBoard;
6572       CopyBoard(testBoard, boards[currentMove]);
6573       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6574
6575       if (CompareBoards(testBoard, boards[currentMove+1])) {
6576         ForwardInner(currentMove+1);
6577
6578         /* Autoplay the opponent's response.
6579          * if appData.animate was TRUE when Training mode was entered,
6580          * the response will be animated.
6581          */
6582         saveAnimate = appData.animate;
6583         appData.animate = animateTraining;
6584         ForwardInner(currentMove+1);
6585         appData.animate = saveAnimate;
6586
6587         /* check for the end of the game */
6588         if (currentMove >= forwardMostMove) {
6589           gameMode = PlayFromGameFile;
6590           ModeHighlight();
6591           SetTrainingModeOff();
6592           DisplayInformation(_("End of game"));
6593         }
6594       } else {
6595         DisplayError(_("Incorrect move"), 0);
6596       }
6597       return 1;
6598     }
6599
6600   /* Ok, now we know that the move is good, so we can kill
6601      the previous line in Analysis Mode */
6602   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6603                                 && currentMove < forwardMostMove) {
6604     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6605     else forwardMostMove = currentMove;
6606   }
6607
6608   /* If we need the chess program but it's dead, restart it */
6609   ResurrectChessProgram();
6610
6611   /* A user move restarts a paused game*/
6612   if (pausing)
6613     PauseEvent();
6614
6615   thinkOutput[0] = NULLCHAR;
6616
6617   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6618
6619   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6620     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6621     return 1;
6622   }
6623
6624   if (gameMode == BeginningOfGame) {
6625     if (appData.noChessProgram) {
6626       gameMode = EditGame;
6627       SetGameInfo();
6628     } else {
6629       char buf[MSG_SIZ];
6630       gameMode = MachinePlaysBlack;
6631       StartClocks();
6632       SetGameInfo();
6633       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6634       DisplayTitle(buf);
6635       if (first.sendName) {
6636         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6637         SendToProgram(buf, &first);
6638       }
6639       StartClocks();
6640     }
6641     ModeHighlight();
6642   }
6643
6644   /* Relay move to ICS or chess engine */
6645   if (appData.icsActive) {
6646     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6647         gameMode == IcsExamining) {
6648       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6649         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6650         SendToICS("draw ");
6651         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6652       }
6653       // also send plain move, in case ICS does not understand atomic claims
6654       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6655       ics_user_moved = 1;
6656     }
6657   } else {
6658     if (first.sendTime && (gameMode == BeginningOfGame ||
6659                            gameMode == MachinePlaysWhite ||
6660                            gameMode == MachinePlaysBlack)) {
6661       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6662     }
6663     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6664          // [HGM] book: if program might be playing, let it use book
6665         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6666         first.maybeThinking = TRUE;
6667     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6668         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6669         SendBoard(&first, currentMove+1);
6670     } else SendMoveToProgram(forwardMostMove-1, &first);
6671     if (currentMove == cmailOldMove + 1) {
6672       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6673     }
6674   }
6675
6676   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6677
6678   switch (gameMode) {
6679   case EditGame:
6680     if(appData.testLegality)
6681     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6682     case MT_NONE:
6683     case MT_CHECK:
6684       break;
6685     case MT_CHECKMATE:
6686     case MT_STAINMATE:
6687       if (WhiteOnMove(currentMove)) {
6688         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6689       } else {
6690         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6691       }
6692       break;
6693     case MT_STALEMATE:
6694       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6695       break;
6696     }
6697     break;
6698
6699   case MachinePlaysBlack:
6700   case MachinePlaysWhite:
6701     /* disable certain menu options while machine is thinking */
6702     SetMachineThinkingEnables();
6703     break;
6704
6705   default:
6706     break;
6707   }
6708
6709   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6710   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6711
6712   if(bookHit) { // [HGM] book: simulate book reply
6713         static char bookMove[MSG_SIZ]; // a bit generous?
6714
6715         programStats.nodes = programStats.depth = programStats.time =
6716         programStats.score = programStats.got_only_move = 0;
6717         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6718
6719         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6720         strcat(bookMove, bookHit);
6721         HandleMachineMove(bookMove, &first);
6722   }
6723   return 1;
6724 }
6725
6726 void
6727 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6728 {
6729     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6730     Markers *m = (Markers *) closure;
6731     if(rf == fromY && ff == fromX)
6732         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6733                          || kind == WhiteCapturesEnPassant
6734                          || kind == BlackCapturesEnPassant);
6735     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6736 }
6737
6738 void
6739 MarkTargetSquares (int clear)
6740 {
6741   int x, y;
6742   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6743      !appData.testLegality || gameMode == EditPosition) return;
6744   if(clear) {
6745     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6746   } else {
6747     int capt = 0;
6748     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6749     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6750       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6751       if(capt)
6752       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6753     }
6754   }
6755   DrawPosition(TRUE, NULL);
6756 }
6757
6758 int
6759 Explode (Board board, int fromX, int fromY, int toX, int toY)
6760 {
6761     if(gameInfo.variant == VariantAtomic &&
6762        (board[toY][toX] != EmptySquare ||                     // capture?
6763         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6764                          board[fromY][fromX] == BlackPawn   )
6765       )) {
6766         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6767         return TRUE;
6768     }
6769     return FALSE;
6770 }
6771
6772 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6773
6774 int
6775 CanPromote (ChessSquare piece, int y)
6776 {
6777         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6778         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6779         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6780            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6781            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6782                                                   gameInfo.variant == VariantMakruk) return FALSE;
6783         return (piece == BlackPawn && y == 1 ||
6784                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6785                 piece == BlackLance && y == 1 ||
6786                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6787 }
6788
6789 void
6790 LeftClick (ClickType clickType, int xPix, int yPix)
6791 {
6792     int x, y;
6793     Boolean saveAnimate;
6794     static int second = 0, promotionChoice = 0, clearFlag = 0;
6795     char promoChoice = NULLCHAR;
6796     ChessSquare piece;
6797
6798     if(appData.seekGraph && appData.icsActive && loggedOn &&
6799         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6800         SeekGraphClick(clickType, xPix, yPix, 0);
6801         return;
6802     }
6803
6804     if (clickType == Press) ErrorPopDown();
6805
6806     x = EventToSquare(xPix, BOARD_WIDTH);
6807     y = EventToSquare(yPix, BOARD_HEIGHT);
6808     if (!flipView && y >= 0) {
6809         y = BOARD_HEIGHT - 1 - y;
6810     }
6811     if (flipView && x >= 0) {
6812         x = BOARD_WIDTH - 1 - x;
6813     }
6814
6815     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6816         defaultPromoChoice = promoSweep;
6817         promoSweep = EmptySquare;   // terminate sweep
6818         promoDefaultAltered = TRUE;
6819         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6820     }
6821
6822     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6823         if(clickType == Release) return; // ignore upclick of click-click destination
6824         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6825         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6826         if(gameInfo.holdingsWidth &&
6827                 (WhiteOnMove(currentMove)
6828                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6829                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6830             // click in right holdings, for determining promotion piece
6831             ChessSquare p = boards[currentMove][y][x];
6832             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6833             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6834             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6835                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6836                 fromX = fromY = -1;
6837                 return;
6838             }
6839         }
6840         DrawPosition(FALSE, boards[currentMove]);
6841         return;
6842     }
6843
6844     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6845     if(clickType == Press
6846             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6847               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6848               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6849         return;
6850
6851     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6852         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6853
6854     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6855         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6856                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6857         defaultPromoChoice = DefaultPromoChoice(side);
6858     }
6859
6860     autoQueen = appData.alwaysPromoteToQueen;
6861
6862     if (fromX == -1) {
6863       int originalY = y;
6864       gatingPiece = EmptySquare;
6865       if (clickType != Press) {
6866         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6867             DragPieceEnd(xPix, yPix); dragging = 0;
6868             DrawPosition(FALSE, NULL);
6869         }
6870         return;
6871       }
6872       fromX = x; fromY = y; toX = toY = -1;
6873       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6874          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6875          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6876             /* First square */
6877             if (OKToStartUserMove(fromX, fromY)) {
6878                 second = 0;
6879                 MarkTargetSquares(0);
6880                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6881                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6882                     promoSweep = defaultPromoChoice;
6883                     selectFlag = 0; lastX = xPix; lastY = yPix;
6884                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6885                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6886                 }
6887                 if (appData.highlightDragging) {
6888                     SetHighlights(fromX, fromY, -1, -1);
6889                 }
6890             } else fromX = fromY = -1;
6891             return;
6892         }
6893     }
6894
6895     /* fromX != -1 */
6896     if (clickType == Press && gameMode != EditPosition) {
6897         ChessSquare fromP;
6898         ChessSquare toP;
6899         int frc;
6900
6901         // ignore off-board to clicks
6902         if(y < 0 || x < 0) return;
6903
6904         /* Check if clicking again on the same color piece */
6905         fromP = boards[currentMove][fromY][fromX];
6906         toP = boards[currentMove][y][x];
6907         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6908         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6909              WhitePawn <= toP && toP <= WhiteKing &&
6910              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6911              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6912             (BlackPawn <= fromP && fromP <= BlackKing &&
6913              BlackPawn <= toP && toP <= BlackKing &&
6914              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6915              !(fromP == BlackKing && toP == BlackRook && frc))) {
6916             /* Clicked again on same color piece -- changed his mind */
6917             second = (x == fromX && y == fromY);
6918             promoDefaultAltered = FALSE;
6919             MarkTargetSquares(1);
6920            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6921             if (appData.highlightDragging) {
6922                 SetHighlights(x, y, -1, -1);
6923             } else {
6924                 ClearHighlights();
6925             }
6926             if (OKToStartUserMove(x, y)) {
6927                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6928                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6929                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6930                  gatingPiece = boards[currentMove][fromY][fromX];
6931                 else gatingPiece = EmptySquare;
6932                 fromX = x;
6933                 fromY = y; dragging = 1;
6934                 MarkTargetSquares(0);
6935                 DragPieceBegin(xPix, yPix, FALSE);
6936                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6937                     promoSweep = defaultPromoChoice;
6938                     selectFlag = 0; lastX = xPix; lastY = yPix;
6939                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6940                 }
6941             }
6942            }
6943            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6944            second = FALSE; 
6945         }
6946         // ignore clicks on holdings
6947         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6948     }
6949
6950     if (clickType == Release && x == fromX && y == fromY) {
6951         DragPieceEnd(xPix, yPix); dragging = 0;
6952         if(clearFlag) {
6953             // a deferred attempt to click-click move an empty square on top of a piece
6954             boards[currentMove][y][x] = EmptySquare;
6955             ClearHighlights();
6956             DrawPosition(FALSE, boards[currentMove]);
6957             fromX = fromY = -1; clearFlag = 0;
6958             return;
6959         }
6960         if (appData.animateDragging) {
6961             /* Undo animation damage if any */
6962             DrawPosition(FALSE, NULL);
6963         }
6964         if (second) {
6965             /* Second up/down in same square; just abort move */
6966             second = 0;
6967             fromX = fromY = -1;
6968             gatingPiece = EmptySquare;
6969             ClearHighlights();
6970             gotPremove = 0;
6971             ClearPremoveHighlights();
6972         } else {
6973             /* First upclick in same square; start click-click mode */
6974             SetHighlights(x, y, -1, -1);
6975         }
6976         return;
6977     }
6978
6979     clearFlag = 0;
6980
6981     /* we now have a different from- and (possibly off-board) to-square */
6982     /* Completed move */
6983     toX = x;
6984     toY = y;
6985     saveAnimate = appData.animate;
6986     MarkTargetSquares(1);
6987     if (clickType == Press) {
6988         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6989             // must be Edit Position mode with empty-square selected
6990             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
6991             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6992             return;
6993         }
6994         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
6995             ChessSquare piece = boards[currentMove][fromY][fromX];
6996             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
6997             promoSweep = defaultPromoChoice;
6998             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
6999             selectFlag = 0; lastX = xPix; lastY = yPix;
7000             Sweep(0); // Pawn that is going to promote: preview promotion piece
7001             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7002             DrawPosition(FALSE, boards[currentMove]);
7003             return;
7004         }
7005         /* Finish clickclick move */
7006         if (appData.animate || appData.highlightLastMove) {
7007             SetHighlights(fromX, fromY, toX, toY);
7008         } else {
7009             ClearHighlights();
7010         }
7011     } else {
7012         /* Finish drag move */
7013         if (appData.highlightLastMove) {
7014             SetHighlights(fromX, fromY, toX, toY);
7015         } else {
7016             ClearHighlights();
7017         }
7018         DragPieceEnd(xPix, yPix); dragging = 0;
7019         /* Don't animate move and drag both */
7020         appData.animate = FALSE;
7021     }
7022
7023     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7024     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7025         ChessSquare piece = boards[currentMove][fromY][fromX];
7026         if(gameMode == EditPosition && piece != EmptySquare &&
7027            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7028             int n;
7029
7030             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7031                 n = PieceToNumber(piece - (int)BlackPawn);
7032                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7033                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7034                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7035             } else
7036             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7037                 n = PieceToNumber(piece);
7038                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7039                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7040                 boards[currentMove][n][BOARD_WIDTH-2]++;
7041             }
7042             boards[currentMove][fromY][fromX] = EmptySquare;
7043         }
7044         ClearHighlights();
7045         fromX = fromY = -1;
7046         DrawPosition(TRUE, boards[currentMove]);
7047         return;
7048     }
7049
7050     // off-board moves should not be highlighted
7051     if(x < 0 || y < 0) ClearHighlights();
7052
7053     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7054
7055     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7056         SetHighlights(fromX, fromY, toX, toY);
7057         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7058             // [HGM] super: promotion to captured piece selected from holdings
7059             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7060             promotionChoice = TRUE;
7061             // kludge follows to temporarily execute move on display, without promoting yet
7062             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7063             boards[currentMove][toY][toX] = p;
7064             DrawPosition(FALSE, boards[currentMove]);
7065             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7066             boards[currentMove][toY][toX] = q;
7067             DisplayMessage("Click in holdings to choose piece", "");
7068             return;
7069         }
7070         PromotionPopUp();
7071     } else {
7072         int oldMove = currentMove;
7073         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7074         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7075         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7076         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7077            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7078             DrawPosition(TRUE, boards[currentMove]);
7079         fromX = fromY = -1;
7080     }
7081     appData.animate = saveAnimate;
7082     if (appData.animate || appData.animateDragging) {
7083         /* Undo animation damage if needed */
7084         DrawPosition(FALSE, NULL);
7085     }
7086 }
7087
7088 int
7089 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7090 {   // front-end-free part taken out of PieceMenuPopup
7091     int whichMenu; int xSqr, ySqr;
7092
7093     if(seekGraphUp) { // [HGM] seekgraph
7094         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7095         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7096         return -2;
7097     }
7098
7099     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7100          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7101         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7102         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7103         if(action == Press)   {
7104             originalFlip = flipView;
7105             flipView = !flipView; // temporarily flip board to see game from partners perspective
7106             DrawPosition(TRUE, partnerBoard);
7107             DisplayMessage(partnerStatus, "");
7108             partnerUp = TRUE;
7109         } else if(action == Release) {
7110             flipView = originalFlip;
7111             DrawPosition(TRUE, boards[currentMove]);
7112             partnerUp = FALSE;
7113         }
7114         return -2;
7115     }
7116
7117     xSqr = EventToSquare(x, BOARD_WIDTH);
7118     ySqr = EventToSquare(y, BOARD_HEIGHT);
7119     if (action == Release) {
7120         if(pieceSweep != EmptySquare) {
7121             EditPositionMenuEvent(pieceSweep, toX, toY);
7122             pieceSweep = EmptySquare;
7123         } else UnLoadPV(); // [HGM] pv
7124     }
7125     if (action != Press) return -2; // return code to be ignored
7126     switch (gameMode) {
7127       case IcsExamining:
7128         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7129       case EditPosition:
7130         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7131         if (xSqr < 0 || ySqr < 0) return -1;
7132         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7133         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7134         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7135         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7136         NextPiece(0);
7137         return 2; // grab
7138       case IcsObserving:
7139         if(!appData.icsEngineAnalyze) return -1;
7140       case IcsPlayingWhite:
7141       case IcsPlayingBlack:
7142         if(!appData.zippyPlay) goto noZip;
7143       case AnalyzeMode:
7144       case AnalyzeFile:
7145       case MachinePlaysWhite:
7146       case MachinePlaysBlack:
7147       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7148         if (!appData.dropMenu) {
7149           LoadPV(x, y);
7150           return 2; // flag front-end to grab mouse events
7151         }
7152         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7153            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7154       case EditGame:
7155       noZip:
7156         if (xSqr < 0 || ySqr < 0) return -1;
7157         if (!appData.dropMenu || appData.testLegality &&
7158             gameInfo.variant != VariantBughouse &&
7159             gameInfo.variant != VariantCrazyhouse) return -1;
7160         whichMenu = 1; // drop menu
7161         break;
7162       default:
7163         return -1;
7164     }
7165
7166     if (((*fromX = xSqr) < 0) ||
7167         ((*fromY = ySqr) < 0)) {
7168         *fromX = *fromY = -1;
7169         return -1;
7170     }
7171     if (flipView)
7172       *fromX = BOARD_WIDTH - 1 - *fromX;
7173     else
7174       *fromY = BOARD_HEIGHT - 1 - *fromY;
7175
7176     return whichMenu;
7177 }
7178
7179 void
7180 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7181 {
7182 //    char * hint = lastHint;
7183     FrontEndProgramStats stats;
7184
7185     stats.which = cps == &first ? 0 : 1;
7186     stats.depth = cpstats->depth;
7187     stats.nodes = cpstats->nodes;
7188     stats.score = cpstats->score;
7189     stats.time = cpstats->time;
7190     stats.pv = cpstats->movelist;
7191     stats.hint = lastHint;
7192     stats.an_move_index = 0;
7193     stats.an_move_count = 0;
7194
7195     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7196         stats.hint = cpstats->move_name;
7197         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7198         stats.an_move_count = cpstats->nr_moves;
7199     }
7200
7201     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
7202
7203     SetProgramStats( &stats );
7204 }
7205
7206 void
7207 ClearEngineOutputPane (int which)
7208 {
7209     static FrontEndProgramStats dummyStats;
7210     dummyStats.which = which;
7211     dummyStats.pv = "#";
7212     SetProgramStats( &dummyStats );
7213 }
7214
7215 #define MAXPLAYERS 500
7216
7217 char *
7218 TourneyStandings (int display)
7219 {
7220     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7221     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7222     char result, *p, *names[MAXPLAYERS];
7223
7224     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7225         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7226     names[0] = p = strdup(appData.participants);
7227     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7228
7229     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7230
7231     while(result = appData.results[nr]) {
7232         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7233         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7234         wScore = bScore = 0;
7235         switch(result) {
7236           case '+': wScore = 2; break;
7237           case '-': bScore = 2; break;
7238           case '=': wScore = bScore = 1; break;
7239           case ' ':
7240           case '*': return strdup("busy"); // tourney not finished
7241         }
7242         score[w] += wScore;
7243         score[b] += bScore;
7244         games[w]++;
7245         games[b]++;
7246         nr++;
7247     }
7248     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7249     for(w=0; w<nPlayers; w++) {
7250         bScore = -1;
7251         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7252         ranking[w] = b; points[w] = bScore; score[b] = -2;
7253     }
7254     p = malloc(nPlayers*34+1);
7255     for(w=0; w<nPlayers && w<display; w++)
7256         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7257     free(names[0]);
7258     return p;
7259 }
7260
7261 void
7262 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7263 {       // count all piece types
7264         int p, f, r;
7265         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7266         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7267         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7268                 p = board[r][f];
7269                 pCnt[p]++;
7270                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7271                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7272                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7273                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7274                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7275                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7276         }
7277 }
7278
7279 int
7280 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7281 {
7282         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7283         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7284
7285         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7286         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7287         if(myPawns == 2 && nMine == 3) // KPP
7288             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7289         if(myPawns == 1 && nMine == 2) // KP
7290             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7291         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7292             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7293         if(myPawns) return FALSE;
7294         if(pCnt[WhiteRook+side])
7295             return pCnt[BlackRook-side] ||
7296                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7297                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7298                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7299         if(pCnt[WhiteCannon+side]) {
7300             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7301             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7302         }
7303         if(pCnt[WhiteKnight+side])
7304             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7305         return FALSE;
7306 }
7307
7308 int
7309 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7310 {
7311         VariantClass v = gameInfo.variant;
7312
7313         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7314         if(v == VariantShatranj) return TRUE; // always winnable through baring
7315         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7316         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7317
7318         if(v == VariantXiangqi) {
7319                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7320
7321                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7322                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7323                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7324                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7325                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7326                 if(stale) // we have at least one last-rank P plus perhaps C
7327                     return majors // KPKX
7328                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7329                 else // KCA*E*
7330                     return pCnt[WhiteFerz+side] // KCAK
7331                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7332                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7333                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7334
7335         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7336                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7337
7338                 if(nMine == 1) return FALSE; // bare King
7339                 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
7340                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7341                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7342                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7343                 if(pCnt[WhiteKnight+side])
7344                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7345                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7346                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7347                 if(nBishops)
7348                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7349                 if(pCnt[WhiteAlfil+side])
7350                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7351                 if(pCnt[WhiteWazir+side])
7352                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7353         }
7354
7355         return TRUE;
7356 }
7357
7358 int
7359 CompareWithRights (Board b1, Board b2)
7360 {
7361     int rights = 0;
7362     if(!CompareBoards(b1, b2)) return FALSE;
7363     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7364     /* compare castling rights */
7365     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7366            rights++; /* King lost rights, while rook still had them */
7367     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7368         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7369            rights++; /* but at least one rook lost them */
7370     }
7371     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7372            rights++;
7373     if( b1[CASTLING][5] != NoRights ) {
7374         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7375            rights++;
7376     }
7377     return rights == 0;
7378 }
7379
7380 int
7381 Adjudicate (ChessProgramState *cps)
7382 {       // [HGM] some adjudications useful with buggy engines
7383         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7384         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7385         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7386         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7387         int k, count = 0; static int bare = 1;
7388         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7389         Boolean canAdjudicate = !appData.icsActive;
7390
7391         // most tests only when we understand the game, i.e. legality-checking on
7392             if( appData.testLegality )
7393             {   /* [HGM] Some more adjudications for obstinate engines */
7394                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7395                 static int moveCount = 6;
7396                 ChessMove result;
7397                 char *reason = NULL;
7398
7399                 /* Count what is on board. */
7400                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7401
7402                 /* Some material-based adjudications that have to be made before stalemate test */
7403                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7404                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7405                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7406                      if(canAdjudicate && appData.checkMates) {
7407                          if(engineOpponent)
7408                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7409                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7410                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7411                          return 1;
7412                      }
7413                 }
7414
7415                 /* Bare King in Shatranj (loses) or Losers (wins) */
7416                 if( nrW == 1 || nrB == 1) {
7417                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7418                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7419                      if(canAdjudicate && appData.checkMates) {
7420                          if(engineOpponent)
7421                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7422                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7423                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7424                          return 1;
7425                      }
7426                   } else
7427                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7428                   {    /* bare King */
7429                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7430                         if(canAdjudicate && appData.checkMates) {
7431                             /* but only adjudicate if adjudication enabled */
7432                             if(engineOpponent)
7433                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7434                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7435                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7436                             return 1;
7437                         }
7438                   }
7439                 } else bare = 1;
7440
7441
7442             // don't wait for engine to announce game end if we can judge ourselves
7443             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7444               case MT_CHECK:
7445                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7446                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7447                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7448                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7449                             checkCnt++;
7450                         if(checkCnt >= 2) {
7451                             reason = "Xboard adjudication: 3rd check";
7452                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7453                             break;
7454                         }
7455                     }
7456                 }
7457               case MT_NONE:
7458               default:
7459                 break;
7460               case MT_STALEMATE:
7461               case MT_STAINMATE:
7462                 reason = "Xboard adjudication: Stalemate";
7463                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7464                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7465                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7466                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7467                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7468                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7469                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7470                                                                         EP_CHECKMATE : EP_WINS);
7471                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7472                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7473                 }
7474                 break;
7475               case MT_CHECKMATE:
7476                 reason = "Xboard adjudication: Checkmate";
7477                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7478                 break;
7479             }
7480
7481                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7482                     case EP_STALEMATE:
7483                         result = GameIsDrawn; break;
7484                     case EP_CHECKMATE:
7485                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7486                     case EP_WINS:
7487                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7488                     default:
7489                         result = EndOfFile;
7490                 }
7491                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7492                     if(engineOpponent)
7493                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7494                     GameEnds( result, reason, GE_XBOARD );
7495                     return 1;
7496                 }
7497
7498                 /* Next absolutely insufficient mating material. */
7499                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7500                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7501                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7502
7503                      /* always flag draws, for judging claims */
7504                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7505
7506                      if(canAdjudicate && appData.materialDraws) {
7507                          /* but only adjudicate them if adjudication enabled */
7508                          if(engineOpponent) {
7509                            SendToProgram("force\n", engineOpponent); // suppress reply
7510                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7511                          }
7512                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7513                          return 1;
7514                      }
7515                 }
7516
7517                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7518                 if(gameInfo.variant == VariantXiangqi ?
7519                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7520                  : nrW + nrB == 4 &&
7521                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7522                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7523                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7524                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7525                    ) ) {
7526                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7527                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7528                           if(engineOpponent) {
7529                             SendToProgram("force\n", engineOpponent); // suppress reply
7530                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7531                           }
7532                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7533                           return 1;
7534                      }
7535                 } else moveCount = 6;
7536             }
7537         if (appData.debugMode) { int i;
7538             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7539                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7540                     appData.drawRepeats);
7541             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7542               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7543
7544         }
7545
7546         // Repetition draws and 50-move rule can be applied independently of legality testing
7547
7548                 /* Check for rep-draws */
7549                 count = 0;
7550                 for(k = forwardMostMove-2;
7551                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7552                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7553                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7554                     k-=2)
7555                 {   int rights=0;
7556                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7557                         /* compare castling rights */
7558                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7559                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7560                                 rights++; /* King lost rights, while rook still had them */
7561                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7562                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7563                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7564                                    rights++; /* but at least one rook lost them */
7565                         }
7566                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7567                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7568                                 rights++;
7569                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7570                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7571                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7572                                    rights++;
7573                         }
7574                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7575                             && appData.drawRepeats > 1) {
7576                              /* adjudicate after user-specified nr of repeats */
7577                              int result = GameIsDrawn;
7578                              char *details = "XBoard adjudication: repetition draw";
7579                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7580                                 // [HGM] xiangqi: check for forbidden perpetuals
7581                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7582                                 for(m=forwardMostMove; m>k; m-=2) {
7583                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7584                                         ourPerpetual = 0; // the current mover did not always check
7585                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7586                                         hisPerpetual = 0; // the opponent did not always check
7587                                 }
7588                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7589                                                                         ourPerpetual, hisPerpetual);
7590                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7591                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7592                                     details = "Xboard adjudication: perpetual checking";
7593                                 } else
7594                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7595                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7596                                 } else
7597                                 // Now check for perpetual chases
7598                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7599                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7600                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7601                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7602                                         static char resdet[MSG_SIZ];
7603                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7604                                         details = resdet;
7605                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7606                                     } else
7607                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7608                                         break; // Abort repetition-checking loop.
7609                                 }
7610                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7611                              }
7612                              if(engineOpponent) {
7613                                SendToProgram("force\n", engineOpponent); // suppress reply
7614                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7615                              }
7616                              GameEnds( result, details, GE_XBOARD );
7617                              return 1;
7618                         }
7619                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7620                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7621                     }
7622                 }
7623
7624                 /* Now we test for 50-move draws. Determine ply count */
7625                 count = forwardMostMove;
7626                 /* look for last irreversble move */
7627                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7628                     count--;
7629                 /* if we hit starting position, add initial plies */
7630                 if( count == backwardMostMove )
7631                     count -= initialRulePlies;
7632                 count = forwardMostMove - count;
7633                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7634                         // adjust reversible move counter for checks in Xiangqi
7635                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7636                         if(i < backwardMostMove) i = backwardMostMove;
7637                         while(i <= forwardMostMove) {
7638                                 lastCheck = inCheck; // check evasion does not count
7639                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7640                                 if(inCheck || lastCheck) count--; // check does not count
7641                                 i++;
7642                         }
7643                 }
7644                 if( count >= 100)
7645                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7646                          /* this is used to judge if draw claims are legal */
7647                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7648                          if(engineOpponent) {
7649                            SendToProgram("force\n", engineOpponent); // suppress reply
7650                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7651                          }
7652                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7653                          return 1;
7654                 }
7655
7656                 /* if draw offer is pending, treat it as a draw claim
7657                  * when draw condition present, to allow engines a way to
7658                  * claim draws before making their move to avoid a race
7659                  * condition occurring after their move
7660                  */
7661                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7662                          char *p = NULL;
7663                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7664                              p = "Draw claim: 50-move rule";
7665                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7666                              p = "Draw claim: 3-fold repetition";
7667                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7668                              p = "Draw claim: insufficient mating material";
7669                          if( p != NULL && canAdjudicate) {
7670                              if(engineOpponent) {
7671                                SendToProgram("force\n", engineOpponent); // suppress reply
7672                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7673                              }
7674                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7675                              return 1;
7676                          }
7677                 }
7678
7679                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7680                     if(engineOpponent) {
7681                       SendToProgram("force\n", engineOpponent); // suppress reply
7682                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7683                     }
7684                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7685                     return 1;
7686                 }
7687         return 0;
7688 }
7689
7690 char *
7691 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7692 {   // [HGM] book: this routine intercepts moves to simulate book replies
7693     char *bookHit = NULL;
7694
7695     //first determine if the incoming move brings opponent into his book
7696     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7697         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7698     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7699     if(bookHit != NULL && !cps->bookSuspend) {
7700         // make sure opponent is not going to reply after receiving move to book position
7701         SendToProgram("force\n", cps);
7702         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7703     }
7704     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7705     // now arrange restart after book miss
7706     if(bookHit) {
7707         // after a book hit we never send 'go', and the code after the call to this routine
7708         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7709         char buf[MSG_SIZ], *move = bookHit;
7710         if(cps->useSAN) {
7711             int fromX, fromY, toX, toY;
7712             char promoChar;
7713             ChessMove moveType;
7714             move = buf + 30;
7715             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7716                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7717                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7718                                     PosFlags(forwardMostMove),
7719                                     fromY, fromX, toY, toX, promoChar, move);
7720             } else {
7721                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7722                 bookHit = NULL;
7723             }
7724         }
7725         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7726         SendToProgram(buf, cps);
7727         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7728     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7729         SendToProgram("go\n", cps);
7730         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7731     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7732         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7733             SendToProgram("go\n", cps);
7734         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7735     }
7736     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7737 }
7738
7739 char *savedMessage;
7740 ChessProgramState *savedState;
7741 void
7742 DeferredBookMove (void)
7743 {
7744         if(savedState->lastPing != savedState->lastPong)
7745                     ScheduleDelayedEvent(DeferredBookMove, 10);
7746         else
7747         HandleMachineMove(savedMessage, savedState);
7748 }
7749
7750 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7751
7752 void
7753 HandleMachineMove (char *message, ChessProgramState *cps)
7754 {
7755     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7756     char realname[MSG_SIZ];
7757     int fromX, fromY, toX, toY;
7758     ChessMove moveType;
7759     char promoChar;
7760     char *p, *pv=buf1;
7761     int machineWhite;
7762     char *bookHit;
7763
7764     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7765         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7766         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7767             DisplayError(_("Invalid pairing from pairing engine"), 0);
7768             return;
7769         }
7770         pairingReceived = 1;
7771         NextMatchGame();
7772         return; // Skim the pairing messages here.
7773     }
7774
7775     cps->userError = 0;
7776
7777 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7778     /*
7779      * Kludge to ignore BEL characters
7780      */
7781     while (*message == '\007') message++;
7782
7783     /*
7784      * [HGM] engine debug message: ignore lines starting with '#' character
7785      */
7786     if(cps->debug && *message == '#') return;
7787
7788     /*
7789      * Look for book output
7790      */
7791     if (cps == &first && bookRequested) {
7792         if (message[0] == '\t' || message[0] == ' ') {
7793             /* Part of the book output is here; append it */
7794             strcat(bookOutput, message);
7795             strcat(bookOutput, "  \n");
7796             return;
7797         } else if (bookOutput[0] != NULLCHAR) {
7798             /* All of book output has arrived; display it */
7799             char *p = bookOutput;
7800             while (*p != NULLCHAR) {
7801                 if (*p == '\t') *p = ' ';
7802                 p++;
7803             }
7804             DisplayInformation(bookOutput);
7805             bookRequested = FALSE;
7806             /* Fall through to parse the current output */
7807         }
7808     }
7809
7810     /*
7811      * Look for machine move.
7812      */
7813     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7814         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7815     {
7816         /* This method is only useful on engines that support ping */
7817         if (cps->lastPing != cps->lastPong) {
7818           if (gameMode == BeginningOfGame) {
7819             /* Extra move from before last new; ignore */
7820             if (appData.debugMode) {
7821                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7822             }
7823           } else {
7824             if (appData.debugMode) {
7825                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7826                         cps->which, gameMode);
7827             }
7828
7829             SendToProgram("undo\n", cps);
7830           }
7831           return;
7832         }
7833
7834         switch (gameMode) {
7835           case BeginningOfGame:
7836             /* Extra move from before last reset; ignore */
7837             if (appData.debugMode) {
7838                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7839             }
7840             return;
7841
7842           case EndOfGame:
7843           case IcsIdle:
7844           default:
7845             /* Extra move after we tried to stop.  The mode test is
7846                not a reliable way of detecting this problem, but it's
7847                the best we can do on engines that don't support ping.
7848             */
7849             if (appData.debugMode) {
7850                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7851                         cps->which, gameMode);
7852             }
7853             SendToProgram("undo\n", cps);
7854             return;
7855
7856           case MachinePlaysWhite:
7857           case IcsPlayingWhite:
7858             machineWhite = TRUE;
7859             break;
7860
7861           case MachinePlaysBlack:
7862           case IcsPlayingBlack:
7863             machineWhite = FALSE;
7864             break;
7865
7866           case TwoMachinesPlay:
7867             machineWhite = (cps->twoMachinesColor[0] == 'w');
7868             break;
7869         }
7870         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7871             if (appData.debugMode) {
7872                 fprintf(debugFP,
7873                         "Ignoring move out of turn by %s, gameMode %d"
7874                         ", forwardMost %d\n",
7875                         cps->which, gameMode, forwardMostMove);
7876             }
7877             return;
7878         }
7879
7880     if (appData.debugMode) { int f = forwardMostMove;
7881         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7882                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7883                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7884     }
7885         if(cps->alphaRank) AlphaRank(machineMove, 4);
7886         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7887                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7888             /* Machine move could not be parsed; ignore it. */
7889           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7890                     machineMove, _(cps->which));
7891             DisplayError(buf1, 0);
7892             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7893                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7894             if (gameMode == TwoMachinesPlay) {
7895               GameEnds(machineWhite ? BlackWins : WhiteWins,
7896                        buf1, GE_XBOARD);
7897             }
7898             return;
7899         }
7900
7901         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7902         /* So we have to redo legality test with true e.p. status here,  */
7903         /* to make sure an illegal e.p. capture does not slip through,   */
7904         /* to cause a forfeit on a justified illegal-move complaint      */
7905         /* of the opponent.                                              */
7906         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7907            ChessMove moveType;
7908            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7909                              fromY, fromX, toY, toX, promoChar);
7910             if (appData.debugMode) {
7911                 int i;
7912                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7913                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7914                 fprintf(debugFP, "castling rights\n");
7915             }
7916             if(moveType == IllegalMove) {
7917               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7918                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7919                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7920                            buf1, GE_XBOARD);
7921                 return;
7922            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7923            /* [HGM] Kludge to handle engines that send FRC-style castling
7924               when they shouldn't (like TSCP-Gothic) */
7925            switch(moveType) {
7926              case WhiteASideCastleFR:
7927              case BlackASideCastleFR:
7928                toX+=2;
7929                currentMoveString[2]++;
7930                break;
7931              case WhiteHSideCastleFR:
7932              case BlackHSideCastleFR:
7933                toX--;
7934                currentMoveString[2]--;
7935                break;
7936              default: ; // nothing to do, but suppresses warning of pedantic compilers
7937            }
7938         }
7939         hintRequested = FALSE;
7940         lastHint[0] = NULLCHAR;
7941         bookRequested = FALSE;
7942         /* Program may be pondering now */
7943         cps->maybeThinking = TRUE;
7944         if (cps->sendTime == 2) cps->sendTime = 1;
7945         if (cps->offeredDraw) cps->offeredDraw--;
7946
7947         /* [AS] Save move info*/
7948         pvInfoList[ forwardMostMove ].score = programStats.score;
7949         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7950         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7951
7952         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7953
7954         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7955         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7956             int count = 0;
7957
7958             while( count < adjudicateLossPlies ) {
7959                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7960
7961                 if( count & 1 ) {
7962                     score = -score; /* Flip score for winning side */
7963                 }
7964
7965                 if( score > adjudicateLossThreshold ) {
7966                     break;
7967                 }
7968
7969                 count++;
7970             }
7971
7972             if( count >= adjudicateLossPlies ) {
7973                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7974
7975                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7976                     "Xboard adjudication",
7977                     GE_XBOARD );
7978
7979                 return;
7980             }
7981         }
7982
7983         if(Adjudicate(cps)) {
7984             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7985             return; // [HGM] adjudicate: for all automatic game ends
7986         }
7987
7988 #if ZIPPY
7989         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7990             first.initDone) {
7991           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7992                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7993                 SendToICS("draw ");
7994                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7995           }
7996           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7997           ics_user_moved = 1;
7998           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7999                 char buf[3*MSG_SIZ];
8000
8001                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8002                         programStats.score / 100.,
8003                         programStats.depth,
8004                         programStats.time / 100.,
8005                         (unsigned int)programStats.nodes,
8006                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8007                         programStats.movelist);
8008                 SendToICS(buf);
8009 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8010           }
8011         }
8012 #endif
8013
8014         /* [AS] Clear stats for next move */
8015         ClearProgramStats();
8016         thinkOutput[0] = NULLCHAR;
8017         hiddenThinkOutputState = 0;
8018
8019         bookHit = NULL;
8020         if (gameMode == TwoMachinesPlay) {
8021             /* [HGM] relaying draw offers moved to after reception of move */
8022             /* and interpreting offer as claim if it brings draw condition */
8023             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8024                 SendToProgram("draw\n", cps->other);
8025             }
8026             if (cps->other->sendTime) {
8027                 SendTimeRemaining(cps->other,
8028                                   cps->other->twoMachinesColor[0] == 'w');
8029             }
8030             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8031             if (firstMove && !bookHit) {
8032                 firstMove = FALSE;
8033                 if (cps->other->useColors) {
8034                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8035                 }
8036                 SendToProgram("go\n", cps->other);
8037             }
8038             cps->other->maybeThinking = TRUE;
8039         }
8040
8041         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8042
8043         if (!pausing && appData.ringBellAfterMoves) {
8044             RingBell();
8045         }
8046
8047         /*
8048          * Reenable menu items that were disabled while
8049          * machine was thinking
8050          */
8051         if (gameMode != TwoMachinesPlay)
8052             SetUserThinkingEnables();
8053
8054         // [HGM] book: after book hit opponent has received move and is now in force mode
8055         // force the book reply into it, and then fake that it outputted this move by jumping
8056         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8057         if(bookHit) {
8058                 static char bookMove[MSG_SIZ]; // a bit generous?
8059
8060                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8061                 strcat(bookMove, bookHit);
8062                 message = bookMove;
8063                 cps = cps->other;
8064                 programStats.nodes = programStats.depth = programStats.time =
8065                 programStats.score = programStats.got_only_move = 0;
8066                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8067
8068                 if(cps->lastPing != cps->lastPong) {
8069                     savedMessage = message; // args for deferred call
8070                     savedState = cps;
8071                     ScheduleDelayedEvent(DeferredBookMove, 10);
8072                     return;
8073                 }
8074                 goto FakeBookMove;
8075         }
8076
8077         return;
8078     }
8079
8080     /* Set special modes for chess engines.  Later something general
8081      *  could be added here; for now there is just one kludge feature,
8082      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8083      *  when "xboard" is given as an interactive command.
8084      */
8085     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8086         cps->useSigint = FALSE;
8087         cps->useSigterm = FALSE;
8088     }
8089     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8090       ParseFeatures(message+8, cps);
8091       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8092     }
8093
8094     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8095                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8096       int dummy, s=6; char buf[MSG_SIZ];
8097       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8098       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8099       if(startedFromSetupPosition) return;
8100       ParseFEN(boards[0], &dummy, message+s);
8101       DrawPosition(TRUE, boards[0]);
8102       startedFromSetupPosition = TRUE;
8103       return;
8104     }
8105     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8106      * want this, I was asked to put it in, and obliged.
8107      */
8108     if (!strncmp(message, "setboard ", 9)) {
8109         Board initial_position;
8110
8111         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8112
8113         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8114             DisplayError(_("Bad FEN received from engine"), 0);
8115             return ;
8116         } else {
8117            Reset(TRUE, FALSE);
8118            CopyBoard(boards[0], initial_position);
8119            initialRulePlies = FENrulePlies;
8120            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8121            else gameMode = MachinePlaysBlack;
8122            DrawPosition(FALSE, boards[currentMove]);
8123         }
8124         return;
8125     }
8126
8127     /*
8128      * Look for communication commands
8129      */
8130     if (!strncmp(message, "telluser ", 9)) {
8131         if(message[9] == '\\' && message[10] == '\\')
8132             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8133         PlayTellSound();
8134         DisplayNote(message + 9);
8135         return;
8136     }
8137     if (!strncmp(message, "tellusererror ", 14)) {
8138         cps->userError = 1;
8139         if(message[14] == '\\' && message[15] == '\\')
8140             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8141         PlayTellSound();
8142         DisplayError(message + 14, 0);
8143         return;
8144     }
8145     if (!strncmp(message, "tellopponent ", 13)) {
8146       if (appData.icsActive) {
8147         if (loggedOn) {
8148           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8149           SendToICS(buf1);
8150         }
8151       } else {
8152         DisplayNote(message + 13);
8153       }
8154       return;
8155     }
8156     if (!strncmp(message, "tellothers ", 11)) {
8157       if (appData.icsActive) {
8158         if (loggedOn) {
8159           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8160           SendToICS(buf1);
8161         }
8162       }
8163       return;
8164     }
8165     if (!strncmp(message, "tellall ", 8)) {
8166       if (appData.icsActive) {
8167         if (loggedOn) {
8168           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8169           SendToICS(buf1);
8170         }
8171       } else {
8172         DisplayNote(message + 8);
8173       }
8174       return;
8175     }
8176     if (strncmp(message, "warning", 7) == 0) {
8177         /* Undocumented feature, use tellusererror in new code */
8178         DisplayError(message, 0);
8179         return;
8180     }
8181     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8182         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8183         strcat(realname, " query");
8184         AskQuestion(realname, buf2, buf1, cps->pr);
8185         return;
8186     }
8187     /* Commands from the engine directly to ICS.  We don't allow these to be
8188      *  sent until we are logged on. Crafty kibitzes have been known to
8189      *  interfere with the login process.
8190      */
8191     if (loggedOn) {
8192         if (!strncmp(message, "tellics ", 8)) {
8193             SendToICS(message + 8);
8194             SendToICS("\n");
8195             return;
8196         }
8197         if (!strncmp(message, "tellicsnoalias ", 15)) {
8198             SendToICS(ics_prefix);
8199             SendToICS(message + 15);
8200             SendToICS("\n");
8201             return;
8202         }
8203         /* The following are for backward compatibility only */
8204         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8205             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8206             SendToICS(ics_prefix);
8207             SendToICS(message);
8208             SendToICS("\n");
8209             return;
8210         }
8211     }
8212     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8213         return;
8214     }
8215     /*
8216      * If the move is illegal, cancel it and redraw the board.
8217      * Also deal with other error cases.  Matching is rather loose
8218      * here to accommodate engines written before the spec.
8219      */
8220     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8221         strncmp(message, "Error", 5) == 0) {
8222         if (StrStr(message, "name") ||
8223             StrStr(message, "rating") || StrStr(message, "?") ||
8224             StrStr(message, "result") || StrStr(message, "board") ||
8225             StrStr(message, "bk") || StrStr(message, "computer") ||
8226             StrStr(message, "variant") || StrStr(message, "hint") ||
8227             StrStr(message, "random") || StrStr(message, "depth") ||
8228             StrStr(message, "accepted")) {
8229             return;
8230         }
8231         if (StrStr(message, "protover")) {
8232           /* Program is responding to input, so it's apparently done
8233              initializing, and this error message indicates it is
8234              protocol version 1.  So we don't need to wait any longer
8235              for it to initialize and send feature commands. */
8236           FeatureDone(cps, 1);
8237           cps->protocolVersion = 1;
8238           return;
8239         }
8240         cps->maybeThinking = FALSE;
8241
8242         if (StrStr(message, "draw")) {
8243             /* Program doesn't have "draw" command */
8244             cps->sendDrawOffers = 0;
8245             return;
8246         }
8247         if (cps->sendTime != 1 &&
8248             (StrStr(message, "time") || StrStr(message, "otim"))) {
8249           /* Program apparently doesn't have "time" or "otim" command */
8250           cps->sendTime = 0;
8251           return;
8252         }
8253         if (StrStr(message, "analyze")) {
8254             cps->analysisSupport = FALSE;
8255             cps->analyzing = FALSE;
8256 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8257             EditGameEvent(); // [HGM] try to preserve loaded game
8258             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8259             DisplayError(buf2, 0);
8260             return;
8261         }
8262         if (StrStr(message, "(no matching move)st")) {
8263           /* Special kludge for GNU Chess 4 only */
8264           cps->stKludge = TRUE;
8265           SendTimeControl(cps, movesPerSession, timeControl,
8266                           timeIncrement, appData.searchDepth,
8267                           searchTime);
8268           return;
8269         }
8270         if (StrStr(message, "(no matching move)sd")) {
8271           /* Special kludge for GNU Chess 4 only */
8272           cps->sdKludge = TRUE;
8273           SendTimeControl(cps, movesPerSession, timeControl,
8274                           timeIncrement, appData.searchDepth,
8275                           searchTime);
8276           return;
8277         }
8278         if (!StrStr(message, "llegal")) {
8279             return;
8280         }
8281         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8282             gameMode == IcsIdle) return;
8283         if (forwardMostMove <= backwardMostMove) return;
8284         if (pausing) PauseEvent();
8285       if(appData.forceIllegal) {
8286             // [HGM] illegal: machine refused move; force position after move into it
8287           SendToProgram("force\n", cps);
8288           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8289                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8290                 // when black is to move, while there might be nothing on a2 or black
8291                 // might already have the move. So send the board as if white has the move.
8292                 // But first we must change the stm of the engine, as it refused the last move
8293                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8294                 if(WhiteOnMove(forwardMostMove)) {
8295                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8296                     SendBoard(cps, forwardMostMove); // kludgeless board
8297                 } else {
8298                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8299                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8300                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8301                 }
8302           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8303             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8304                  gameMode == TwoMachinesPlay)
8305               SendToProgram("go\n", cps);
8306             return;
8307       } else
8308         if (gameMode == PlayFromGameFile) {
8309             /* Stop reading this game file */
8310             gameMode = EditGame;
8311             ModeHighlight();
8312         }
8313         /* [HGM] illegal-move claim should forfeit game when Xboard */
8314         /* only passes fully legal moves                            */
8315         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8316             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8317                                 "False illegal-move claim", GE_XBOARD );
8318             return; // do not take back move we tested as valid
8319         }
8320         currentMove = forwardMostMove-1;
8321         DisplayMove(currentMove-1); /* before DisplayMoveError */
8322         SwitchClocks(forwardMostMove-1); // [HGM] race
8323         DisplayBothClocks();
8324         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8325                 parseList[currentMove], _(cps->which));
8326         DisplayMoveError(buf1);
8327         DrawPosition(FALSE, boards[currentMove]);
8328
8329         SetUserThinkingEnables();
8330         return;
8331     }
8332     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8333         /* Program has a broken "time" command that
8334            outputs a string not ending in newline.
8335            Don't use it. */
8336         cps->sendTime = 0;
8337     }
8338
8339     /*
8340      * If chess program startup fails, exit with an error message.
8341      * Attempts to recover here are futile.
8342      */
8343     if ((StrStr(message, "unknown host") != NULL)
8344         || (StrStr(message, "No remote directory") != NULL)
8345         || (StrStr(message, "not found") != NULL)
8346         || (StrStr(message, "No such file") != NULL)
8347         || (StrStr(message, "can't alloc") != NULL)
8348         || (StrStr(message, "Permission denied") != NULL)) {
8349
8350         cps->maybeThinking = FALSE;
8351         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8352                 _(cps->which), cps->program, cps->host, message);
8353         RemoveInputSource(cps->isr);
8354         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8355             if(cps == &first) appData.noChessProgram = TRUE;
8356             DisplayError(buf1, 0);
8357         }
8358         return;
8359     }
8360
8361     /*
8362      * Look for hint output
8363      */
8364     if (sscanf(message, "Hint: %s", buf1) == 1) {
8365         if (cps == &first && hintRequested) {
8366             hintRequested = FALSE;
8367             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8368                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8369                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8370                                     PosFlags(forwardMostMove),
8371                                     fromY, fromX, toY, toX, promoChar, buf1);
8372                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8373                 DisplayInformation(buf2);
8374             } else {
8375                 /* Hint move could not be parsed!? */
8376               snprintf(buf2, sizeof(buf2),
8377                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8378                         buf1, _(cps->which));
8379                 DisplayError(buf2, 0);
8380             }
8381         } else {
8382           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8383         }
8384         return;
8385     }
8386
8387     /*
8388      * Ignore other messages if game is not in progress
8389      */
8390     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8391         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8392
8393     /*
8394      * look for win, lose, draw, or draw offer
8395      */
8396     if (strncmp(message, "1-0", 3) == 0) {
8397         char *p, *q, *r = "";
8398         p = strchr(message, '{');
8399         if (p) {
8400             q = strchr(p, '}');
8401             if (q) {
8402                 *q = NULLCHAR;
8403                 r = p + 1;
8404             }
8405         }
8406         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8407         return;
8408     } else if (strncmp(message, "0-1", 3) == 0) {
8409         char *p, *q, *r = "";
8410         p = strchr(message, '{');
8411         if (p) {
8412             q = strchr(p, '}');
8413             if (q) {
8414                 *q = NULLCHAR;
8415                 r = p + 1;
8416             }
8417         }
8418         /* Kludge for Arasan 4.1 bug */
8419         if (strcmp(r, "Black resigns") == 0) {
8420             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8421             return;
8422         }
8423         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8424         return;
8425     } else if (strncmp(message, "1/2", 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
8436         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8437         return;
8438
8439     } else if (strncmp(message, "White resign", 12) == 0) {
8440         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8441         return;
8442     } else if (strncmp(message, "Black resign", 12) == 0) {
8443         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8444         return;
8445     } else if (strncmp(message, "White matches", 13) == 0 ||
8446                strncmp(message, "Black matches", 13) == 0   ) {
8447         /* [HGM] ignore GNUShogi noises */
8448         return;
8449     } else if (strncmp(message, "White", 5) == 0 &&
8450                message[5] != '(' &&
8451                StrStr(message, "Black") == NULL) {
8452         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8453         return;
8454     } else if (strncmp(message, "Black", 5) == 0 &&
8455                message[5] != '(') {
8456         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8457         return;
8458     } else if (strcmp(message, "resign") == 0 ||
8459                strcmp(message, "computer resigns") == 0) {
8460         switch (gameMode) {
8461           case MachinePlaysBlack:
8462           case IcsPlayingBlack:
8463             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8464             break;
8465           case MachinePlaysWhite:
8466           case IcsPlayingWhite:
8467             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8468             break;
8469           case TwoMachinesPlay:
8470             if (cps->twoMachinesColor[0] == 'w')
8471               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8472             else
8473               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8474             break;
8475           default:
8476             /* can't happen */
8477             break;
8478         }
8479         return;
8480     } else if (strncmp(message, "opponent mates", 14) == 0) {
8481         switch (gameMode) {
8482           case MachinePlaysBlack:
8483           case IcsPlayingBlack:
8484             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8485             break;
8486           case MachinePlaysWhite:
8487           case IcsPlayingWhite:
8488             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8489             break;
8490           case TwoMachinesPlay:
8491             if (cps->twoMachinesColor[0] == 'w')
8492               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8493             else
8494               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8495             break;
8496           default:
8497             /* can't happen */
8498             break;
8499         }
8500         return;
8501     } else if (strncmp(message, "computer mates", 14) == 0) {
8502         switch (gameMode) {
8503           case MachinePlaysBlack:
8504           case IcsPlayingBlack:
8505             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8506             break;
8507           case MachinePlaysWhite:
8508           case IcsPlayingWhite:
8509             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8510             break;
8511           case TwoMachinesPlay:
8512             if (cps->twoMachinesColor[0] == 'w')
8513               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8514             else
8515               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8516             break;
8517           default:
8518             /* can't happen */
8519             break;
8520         }
8521         return;
8522     } else if (strncmp(message, "checkmate", 9) == 0) {
8523         if (WhiteOnMove(forwardMostMove)) {
8524             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8525         } else {
8526             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8527         }
8528         return;
8529     } else if (strstr(message, "Draw") != NULL ||
8530                strstr(message, "game is a draw") != NULL) {
8531         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8532         return;
8533     } else if (strstr(message, "offer") != NULL &&
8534                strstr(message, "draw") != NULL) {
8535 #if ZIPPY
8536         if (appData.zippyPlay && first.initDone) {
8537             /* Relay offer to ICS */
8538             SendToICS(ics_prefix);
8539             SendToICS("draw\n");
8540         }
8541 #endif
8542         cps->offeredDraw = 2; /* valid until this engine moves twice */
8543         if (gameMode == TwoMachinesPlay) {
8544             if (cps->other->offeredDraw) {
8545                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8546             /* [HGM] in two-machine mode we delay relaying draw offer      */
8547             /* until after we also have move, to see if it is really claim */
8548             }
8549         } else if (gameMode == MachinePlaysWhite ||
8550                    gameMode == MachinePlaysBlack) {
8551           if (userOfferedDraw) {
8552             DisplayInformation(_("Machine accepts your draw offer"));
8553             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8554           } else {
8555             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8556           }
8557         }
8558     }
8559
8560
8561     /*
8562      * Look for thinking output
8563      */
8564     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8565           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8566                                 ) {
8567         int plylev, mvleft, mvtot, curscore, time;
8568         char mvname[MOVE_LEN];
8569         u64 nodes; // [DM]
8570         char plyext;
8571         int ignore = FALSE;
8572         int prefixHint = FALSE;
8573         mvname[0] = NULLCHAR;
8574
8575         switch (gameMode) {
8576           case MachinePlaysBlack:
8577           case IcsPlayingBlack:
8578             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8579             break;
8580           case MachinePlaysWhite:
8581           case IcsPlayingWhite:
8582             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8583             break;
8584           case AnalyzeMode:
8585           case AnalyzeFile:
8586             break;
8587           case IcsObserving: /* [DM] icsEngineAnalyze */
8588             if (!appData.icsEngineAnalyze) ignore = TRUE;
8589             break;
8590           case TwoMachinesPlay:
8591             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8592                 ignore = TRUE;
8593             }
8594             break;
8595           default:
8596             ignore = TRUE;
8597             break;
8598         }
8599
8600         if (!ignore) {
8601             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8602             buf1[0] = NULLCHAR;
8603             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8604                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8605
8606                 if (plyext != ' ' && plyext != '\t') {
8607                     time *= 100;
8608                 }
8609
8610                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8611                 if( cps->scoreIsAbsolute &&
8612                     ( gameMode == MachinePlaysBlack ||
8613                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8614                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8615                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8616                      !WhiteOnMove(currentMove)
8617                     ) )
8618                 {
8619                     curscore = -curscore;
8620                 }
8621
8622                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8623
8624                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8625                         char buf[MSG_SIZ];
8626                         FILE *f;
8627                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8628                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8629                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8630                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8631                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8632                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8633                                 fclose(f);
8634                         } else DisplayError(_("failed writing PV"), 0);
8635                 }
8636
8637                 tempStats.depth = plylev;
8638                 tempStats.nodes = nodes;
8639                 tempStats.time = time;
8640                 tempStats.score = curscore;
8641                 tempStats.got_only_move = 0;
8642
8643                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8644                         int ticklen;
8645
8646                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8647                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8648                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8649                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8650                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8651                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8652                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8653                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8654                 }
8655
8656                 /* Buffer overflow protection */
8657                 if (pv[0] != NULLCHAR) {
8658                     if (strlen(pv) >= sizeof(tempStats.movelist)
8659                         && appData.debugMode) {
8660                         fprintf(debugFP,
8661                                 "PV is too long; using the first %u bytes.\n",
8662                                 (unsigned) sizeof(tempStats.movelist) - 1);
8663                     }
8664
8665                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8666                 } else {
8667                     sprintf(tempStats.movelist, " no PV\n");
8668                 }
8669
8670                 if (tempStats.seen_stat) {
8671                     tempStats.ok_to_send = 1;
8672                 }
8673
8674                 if (strchr(tempStats.movelist, '(') != NULL) {
8675                     tempStats.line_is_book = 1;
8676                     tempStats.nr_moves = 0;
8677                     tempStats.moves_left = 0;
8678                 } else {
8679                     tempStats.line_is_book = 0;
8680                 }
8681
8682                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8683                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8684
8685                 SendProgramStatsToFrontend( cps, &tempStats );
8686
8687                 /*
8688                     [AS] Protect the thinkOutput buffer from overflow... this
8689                     is only useful if buf1 hasn't overflowed first!
8690                 */
8691                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8692                          plylev,
8693                          (gameMode == TwoMachinesPlay ?
8694                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8695                          ((double) curscore) / 100.0,
8696                          prefixHint ? lastHint : "",
8697                          prefixHint ? " " : "" );
8698
8699                 if( buf1[0] != NULLCHAR ) {
8700                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8701
8702                     if( strlen(pv) > max_len ) {
8703                         if( appData.debugMode) {
8704                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8705                         }
8706                         pv[max_len+1] = '\0';
8707                     }
8708
8709                     strcat( thinkOutput, pv);
8710                 }
8711
8712                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8713                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8714                     DisplayMove(currentMove - 1);
8715                 }
8716                 return;
8717
8718             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8719                 /* crafty (9.25+) says "(only move) <move>"
8720                  * if there is only 1 legal move
8721                  */
8722                 sscanf(p, "(only move) %s", buf1);
8723                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8724                 sprintf(programStats.movelist, "%s (only move)", buf1);
8725                 programStats.depth = 1;
8726                 programStats.nr_moves = 1;
8727                 programStats.moves_left = 1;
8728                 programStats.nodes = 1;
8729                 programStats.time = 1;
8730                 programStats.got_only_move = 1;
8731
8732                 /* Not really, but we also use this member to
8733                    mean "line isn't going to change" (Crafty
8734                    isn't searching, so stats won't change) */
8735                 programStats.line_is_book = 1;
8736
8737                 SendProgramStatsToFrontend( cps, &programStats );
8738
8739                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8740                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8741                     DisplayMove(currentMove - 1);
8742                 }
8743                 return;
8744             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8745                               &time, &nodes, &plylev, &mvleft,
8746                               &mvtot, mvname) >= 5) {
8747                 /* The stat01: line is from Crafty (9.29+) in response
8748                    to the "." command */
8749                 programStats.seen_stat = 1;
8750                 cps->maybeThinking = TRUE;
8751
8752                 if (programStats.got_only_move || !appData.periodicUpdates)
8753                   return;
8754
8755                 programStats.depth = plylev;
8756                 programStats.time = time;
8757                 programStats.nodes = nodes;
8758                 programStats.moves_left = mvleft;
8759                 programStats.nr_moves = mvtot;
8760                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8761                 programStats.ok_to_send = 1;
8762                 programStats.movelist[0] = '\0';
8763
8764                 SendProgramStatsToFrontend( cps, &programStats );
8765
8766                 return;
8767
8768             } else if (strncmp(message,"++",2) == 0) {
8769                 /* Crafty 9.29+ outputs this */
8770                 programStats.got_fail = 2;
8771                 return;
8772
8773             } else if (strncmp(message,"--",2) == 0) {
8774                 /* Crafty 9.29+ outputs this */
8775                 programStats.got_fail = 1;
8776                 return;
8777
8778             } else if (thinkOutput[0] != NULLCHAR &&
8779                        strncmp(message, "    ", 4) == 0) {
8780                 unsigned message_len;
8781
8782                 p = message;
8783                 while (*p && *p == ' ') p++;
8784
8785                 message_len = strlen( p );
8786
8787                 /* [AS] Avoid buffer overflow */
8788                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8789                     strcat(thinkOutput, " ");
8790                     strcat(thinkOutput, p);
8791                 }
8792
8793                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8794                     strcat(programStats.movelist, " ");
8795                     strcat(programStats.movelist, p);
8796                 }
8797
8798                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8799                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8800                     DisplayMove(currentMove - 1);
8801                 }
8802                 return;
8803             }
8804         }
8805         else {
8806             buf1[0] = NULLCHAR;
8807
8808             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8809                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8810             {
8811                 ChessProgramStats cpstats;
8812
8813                 if (plyext != ' ' && plyext != '\t') {
8814                     time *= 100;
8815                 }
8816
8817                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8818                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8819                     curscore = -curscore;
8820                 }
8821
8822                 cpstats.depth = plylev;
8823                 cpstats.nodes = nodes;
8824                 cpstats.time = time;
8825                 cpstats.score = curscore;
8826                 cpstats.got_only_move = 0;
8827                 cpstats.movelist[0] = '\0';
8828
8829                 if (buf1[0] != NULLCHAR) {
8830                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8831                 }
8832
8833                 cpstats.ok_to_send = 0;
8834                 cpstats.line_is_book = 0;
8835                 cpstats.nr_moves = 0;
8836                 cpstats.moves_left = 0;
8837
8838                 SendProgramStatsToFrontend( cps, &cpstats );
8839             }
8840         }
8841     }
8842 }
8843
8844
8845 /* Parse a game score from the character string "game", and
8846    record it as the history of the current game.  The game
8847    score is NOT assumed to start from the standard position.
8848    The display is not updated in any way.
8849    */
8850 void
8851 ParseGameHistory (char *game)
8852 {
8853     ChessMove moveType;
8854     int fromX, fromY, toX, toY, boardIndex;
8855     char promoChar;
8856     char *p, *q;
8857     char buf[MSG_SIZ];
8858
8859     if (appData.debugMode)
8860       fprintf(debugFP, "Parsing game history: %s\n", game);
8861
8862     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8863     gameInfo.site = StrSave(appData.icsHost);
8864     gameInfo.date = PGNDate();
8865     gameInfo.round = StrSave("-");
8866
8867     /* Parse out names of players */
8868     while (*game == ' ') game++;
8869     p = buf;
8870     while (*game != ' ') *p++ = *game++;
8871     *p = NULLCHAR;
8872     gameInfo.white = StrSave(buf);
8873     while (*game == ' ') game++;
8874     p = buf;
8875     while (*game != ' ' && *game != '\n') *p++ = *game++;
8876     *p = NULLCHAR;
8877     gameInfo.black = StrSave(buf);
8878
8879     /* Parse moves */
8880     boardIndex = blackPlaysFirst ? 1 : 0;
8881     yynewstr(game);
8882     for (;;) {
8883         yyboardindex = boardIndex;
8884         moveType = (ChessMove) Myylex();
8885         switch (moveType) {
8886           case IllegalMove:             /* maybe suicide chess, etc. */
8887   if (appData.debugMode) {
8888     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8889     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8890     setbuf(debugFP, NULL);
8891   }
8892           case WhitePromotion:
8893           case BlackPromotion:
8894           case WhiteNonPromotion:
8895           case BlackNonPromotion:
8896           case NormalMove:
8897           case WhiteCapturesEnPassant:
8898           case BlackCapturesEnPassant:
8899           case WhiteKingSideCastle:
8900           case WhiteQueenSideCastle:
8901           case BlackKingSideCastle:
8902           case BlackQueenSideCastle:
8903           case WhiteKingSideCastleWild:
8904           case WhiteQueenSideCastleWild:
8905           case BlackKingSideCastleWild:
8906           case BlackQueenSideCastleWild:
8907           /* PUSH Fabien */
8908           case WhiteHSideCastleFR:
8909           case WhiteASideCastleFR:
8910           case BlackHSideCastleFR:
8911           case BlackASideCastleFR:
8912           /* POP Fabien */
8913             fromX = currentMoveString[0] - AAA;
8914             fromY = currentMoveString[1] - ONE;
8915             toX = currentMoveString[2] - AAA;
8916             toY = currentMoveString[3] - ONE;
8917             promoChar = currentMoveString[4];
8918             break;
8919           case WhiteDrop:
8920           case BlackDrop:
8921             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8922             fromX = moveType == WhiteDrop ?
8923               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8924             (int) CharToPiece(ToLower(currentMoveString[0]));
8925             fromY = DROP_RANK;
8926             toX = currentMoveString[2] - AAA;
8927             toY = currentMoveString[3] - ONE;
8928             promoChar = NULLCHAR;
8929             break;
8930           case AmbiguousMove:
8931             /* bug? */
8932             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8933   if (appData.debugMode) {
8934     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8935     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8936     setbuf(debugFP, NULL);
8937   }
8938             DisplayError(buf, 0);
8939             return;
8940           case ImpossibleMove:
8941             /* bug? */
8942             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8943   if (appData.debugMode) {
8944     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8945     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8946     setbuf(debugFP, NULL);
8947   }
8948             DisplayError(buf, 0);
8949             return;
8950           case EndOfFile:
8951             if (boardIndex < backwardMostMove) {
8952                 /* Oops, gap.  How did that happen? */
8953                 DisplayError(_("Gap in move list"), 0);
8954                 return;
8955             }
8956             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8957             if (boardIndex > forwardMostMove) {
8958                 forwardMostMove = boardIndex;
8959             }
8960             return;
8961           case ElapsedTime:
8962             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8963                 strcat(parseList[boardIndex-1], " ");
8964                 strcat(parseList[boardIndex-1], yy_text);
8965             }
8966             continue;
8967           case Comment:
8968           case PGNTag:
8969           case NAG:
8970           default:
8971             /* ignore */
8972             continue;
8973           case WhiteWins:
8974           case BlackWins:
8975           case GameIsDrawn:
8976           case GameUnfinished:
8977             if (gameMode == IcsExamining) {
8978                 if (boardIndex < backwardMostMove) {
8979                     /* Oops, gap.  How did that happen? */
8980                     return;
8981                 }
8982                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8983                 return;
8984             }
8985             gameInfo.result = moveType;
8986             p = strchr(yy_text, '{');
8987             if (p == NULL) p = strchr(yy_text, '(');
8988             if (p == NULL) {
8989                 p = yy_text;
8990                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8991             } else {
8992                 q = strchr(p, *p == '{' ? '}' : ')');
8993                 if (q != NULL) *q = NULLCHAR;
8994                 p++;
8995             }
8996             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8997             gameInfo.resultDetails = StrSave(p);
8998             continue;
8999         }
9000         if (boardIndex >= forwardMostMove &&
9001             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9002             backwardMostMove = blackPlaysFirst ? 1 : 0;
9003             return;
9004         }
9005         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9006                                  fromY, fromX, toY, toX, promoChar,
9007                                  parseList[boardIndex]);
9008         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9009         /* currentMoveString is set as a side-effect of yylex */
9010         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9011         strcat(moveList[boardIndex], "\n");
9012         boardIndex++;
9013         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9014         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9015           case MT_NONE:
9016           case MT_STALEMATE:
9017           default:
9018             break;
9019           case MT_CHECK:
9020             if(gameInfo.variant != VariantShogi)
9021                 strcat(parseList[boardIndex - 1], "+");
9022             break;
9023           case MT_CHECKMATE:
9024           case MT_STAINMATE:
9025             strcat(parseList[boardIndex - 1], "#");
9026             break;
9027         }
9028     }
9029 }
9030
9031
9032 /* Apply a move to the given board  */
9033 void
9034 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9035 {
9036   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9037   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9038
9039     /* [HGM] compute & store e.p. status and castling rights for new position */
9040     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9041
9042       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9043       oldEP = (signed char)board[EP_STATUS];
9044       board[EP_STATUS] = EP_NONE;
9045
9046   if (fromY == DROP_RANK) {
9047         /* must be first */
9048         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9049             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9050             return;
9051         }
9052         piece = board[toY][toX] = (ChessSquare) fromX;
9053   } else {
9054       int i;
9055
9056       if( board[toY][toX] != EmptySquare )
9057            board[EP_STATUS] = EP_CAPTURE;
9058
9059       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9060            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9061                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9062       } else
9063       if( board[fromY][fromX] == WhitePawn ) {
9064            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9065                board[EP_STATUS] = EP_PAWN_MOVE;
9066            if( toY-fromY==2) {
9067                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9068                         gameInfo.variant != VariantBerolina || toX < fromX)
9069                       board[EP_STATUS] = toX | berolina;
9070                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9071                         gameInfo.variant != VariantBerolina || toX > fromX)
9072                       board[EP_STATUS] = toX;
9073            }
9074       } else
9075       if( board[fromY][fromX] == BlackPawn ) {
9076            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9077                board[EP_STATUS] = EP_PAWN_MOVE;
9078            if( toY-fromY== -2) {
9079                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9080                         gameInfo.variant != VariantBerolina || toX < fromX)
9081                       board[EP_STATUS] = toX | berolina;
9082                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9083                         gameInfo.variant != VariantBerolina || toX > fromX)
9084                       board[EP_STATUS] = toX;
9085            }
9086        }
9087
9088        for(i=0; i<nrCastlingRights; i++) {
9089            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9090               board[CASTLING][i] == toX   && castlingRank[i] == toY
9091              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9092        }
9093
9094      if (fromX == toX && fromY == toY) return;
9095
9096      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9097      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9098      if(gameInfo.variant == VariantKnightmate)
9099          king += (int) WhiteUnicorn - (int) WhiteKing;
9100
9101     /* Code added by Tord: */
9102     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9103     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9104         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9105       board[fromY][fromX] = EmptySquare;
9106       board[toY][toX] = EmptySquare;
9107       if((toX > fromX) != (piece == WhiteRook)) {
9108         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9109       } else {
9110         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9111       }
9112     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9113                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9114       board[fromY][fromX] = EmptySquare;
9115       board[toY][toX] = EmptySquare;
9116       if((toX > fromX) != (piece == BlackRook)) {
9117         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9118       } else {
9119         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9120       }
9121     /* End of code added by Tord */
9122
9123     } else if (board[fromY][fromX] == king
9124         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9125         && toY == fromY && toX > fromX+1) {
9126         board[fromY][fromX] = EmptySquare;
9127         board[toY][toX] = king;
9128         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9129         board[fromY][BOARD_RGHT-1] = EmptySquare;
9130     } else if (board[fromY][fromX] == king
9131         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9132                && toY == fromY && toX < fromX-1) {
9133         board[fromY][fromX] = EmptySquare;
9134         board[toY][toX] = king;
9135         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9136         board[fromY][BOARD_LEFT] = EmptySquare;
9137     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9138                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9139                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9140                ) {
9141         /* white pawn promotion */
9142         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9143         if(gameInfo.variant==VariantBughouse ||
9144            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9145             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9146         board[fromY][fromX] = EmptySquare;
9147     } else if ((fromY >= BOARD_HEIGHT>>1)
9148                && (toX != fromX)
9149                && gameInfo.variant != VariantXiangqi
9150                && gameInfo.variant != VariantBerolina
9151                && (board[fromY][fromX] == WhitePawn)
9152                && (board[toY][toX] == EmptySquare)) {
9153         board[fromY][fromX] = EmptySquare;
9154         board[toY][toX] = WhitePawn;
9155         captured = board[toY - 1][toX];
9156         board[toY - 1][toX] = EmptySquare;
9157     } else if ((fromY == BOARD_HEIGHT-4)
9158                && (toX == fromX)
9159                && gameInfo.variant == VariantBerolina
9160                && (board[fromY][fromX] == WhitePawn)
9161                && (board[toY][toX] == EmptySquare)) {
9162         board[fromY][fromX] = EmptySquare;
9163         board[toY][toX] = WhitePawn;
9164         if(oldEP & EP_BEROLIN_A) {
9165                 captured = board[fromY][fromX-1];
9166                 board[fromY][fromX-1] = EmptySquare;
9167         }else{  captured = board[fromY][fromX+1];
9168                 board[fromY][fromX+1] = EmptySquare;
9169         }
9170     } else if (board[fromY][fromX] == king
9171         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9172                && toY == fromY && toX > fromX+1) {
9173         board[fromY][fromX] = EmptySquare;
9174         board[toY][toX] = king;
9175         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9176         board[fromY][BOARD_RGHT-1] = EmptySquare;
9177     } else if (board[fromY][fromX] == king
9178         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9179                && toY == fromY && toX < fromX-1) {
9180         board[fromY][fromX] = EmptySquare;
9181         board[toY][toX] = king;
9182         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9183         board[fromY][BOARD_LEFT] = EmptySquare;
9184     } else if (fromY == 7 && fromX == 3
9185                && board[fromY][fromX] == BlackKing
9186                && toY == 7 && toX == 5) {
9187         board[fromY][fromX] = EmptySquare;
9188         board[toY][toX] = BlackKing;
9189         board[fromY][7] = EmptySquare;
9190         board[toY][4] = BlackRook;
9191     } else if (fromY == 7 && fromX == 3
9192                && board[fromY][fromX] == BlackKing
9193                && toY == 7 && toX == 1) {
9194         board[fromY][fromX] = EmptySquare;
9195         board[toY][toX] = BlackKing;
9196         board[fromY][0] = EmptySquare;
9197         board[toY][2] = BlackRook;
9198     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9199                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9200                && toY < promoRank && promoChar
9201                ) {
9202         /* black pawn promotion */
9203         board[toY][toX] = CharToPiece(ToLower(promoChar));
9204         if(gameInfo.variant==VariantBughouse ||
9205            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9206             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9207         board[fromY][fromX] = EmptySquare;
9208     } else if ((fromY < BOARD_HEIGHT>>1)
9209                && (toX != fromX)
9210                && gameInfo.variant != VariantXiangqi
9211                && gameInfo.variant != VariantBerolina
9212                && (board[fromY][fromX] == BlackPawn)
9213                && (board[toY][toX] == EmptySquare)) {
9214         board[fromY][fromX] = EmptySquare;
9215         board[toY][toX] = BlackPawn;
9216         captured = board[toY + 1][toX];
9217         board[toY + 1][toX] = EmptySquare;
9218     } else if ((fromY == 3)
9219                && (toX == fromX)
9220                && gameInfo.variant == VariantBerolina
9221                && (board[fromY][fromX] == BlackPawn)
9222                && (board[toY][toX] == EmptySquare)) {
9223         board[fromY][fromX] = EmptySquare;
9224         board[toY][toX] = BlackPawn;
9225         if(oldEP & EP_BEROLIN_A) {
9226                 captured = board[fromY][fromX-1];
9227                 board[fromY][fromX-1] = EmptySquare;
9228         }else{  captured = board[fromY][fromX+1];
9229                 board[fromY][fromX+1] = EmptySquare;
9230         }
9231     } else {
9232         board[toY][toX] = board[fromY][fromX];
9233         board[fromY][fromX] = EmptySquare;
9234     }
9235   }
9236
9237     if (gameInfo.holdingsWidth != 0) {
9238
9239       /* !!A lot more code needs to be written to support holdings  */
9240       /* [HGM] OK, so I have written it. Holdings are stored in the */
9241       /* penultimate board files, so they are automaticlly stored   */
9242       /* in the game history.                                       */
9243       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9244                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9245         /* Delete from holdings, by decreasing count */
9246         /* and erasing image if necessary            */
9247         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9248         if(p < (int) BlackPawn) { /* white drop */
9249              p -= (int)WhitePawn;
9250                  p = PieceToNumber((ChessSquare)p);
9251              if(p >= gameInfo.holdingsSize) p = 0;
9252              if(--board[p][BOARD_WIDTH-2] <= 0)
9253                   board[p][BOARD_WIDTH-1] = EmptySquare;
9254              if((int)board[p][BOARD_WIDTH-2] < 0)
9255                         board[p][BOARD_WIDTH-2] = 0;
9256         } else {                  /* black drop */
9257              p -= (int)BlackPawn;
9258                  p = PieceToNumber((ChessSquare)p);
9259              if(p >= gameInfo.holdingsSize) p = 0;
9260              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9261                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9262              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9263                         board[BOARD_HEIGHT-1-p][1] = 0;
9264         }
9265       }
9266       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9267           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9268         /* [HGM] holdings: Add to holdings, if holdings exist */
9269         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9270                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9271                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9272         }
9273         p = (int) captured;
9274         if (p >= (int) BlackPawn) {
9275           p -= (int)BlackPawn;
9276           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9277                   /* in Shogi restore piece to its original  first */
9278                   captured = (ChessSquare) (DEMOTED captured);
9279                   p = DEMOTED p;
9280           }
9281           p = PieceToNumber((ChessSquare)p);
9282           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9283           board[p][BOARD_WIDTH-2]++;
9284           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9285         } else {
9286           p -= (int)WhitePawn;
9287           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9288                   captured = (ChessSquare) (DEMOTED captured);
9289                   p = DEMOTED p;
9290           }
9291           p = PieceToNumber((ChessSquare)p);
9292           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9293           board[BOARD_HEIGHT-1-p][1]++;
9294           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9295         }
9296       }
9297     } else if (gameInfo.variant == VariantAtomic) {
9298       if (captured != EmptySquare) {
9299         int y, x;
9300         for (y = toY-1; y <= toY+1; y++) {
9301           for (x = toX-1; x <= toX+1; x++) {
9302             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9303                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9304               board[y][x] = EmptySquare;
9305             }
9306           }
9307         }
9308         board[toY][toX] = EmptySquare;
9309       }
9310     }
9311     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9312         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9313     } else
9314     if(promoChar == '+') {
9315         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9316         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9317     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9318         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9319     }
9320     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9321                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9322         // [HGM] superchess: take promotion piece out of holdings
9323         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9324         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9325             if(!--board[k][BOARD_WIDTH-2])
9326                 board[k][BOARD_WIDTH-1] = EmptySquare;
9327         } else {
9328             if(!--board[BOARD_HEIGHT-1-k][1])
9329                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9330         }
9331     }
9332
9333 }
9334
9335 /* Updates forwardMostMove */
9336 void
9337 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9338 {
9339 //    forwardMostMove++; // [HGM] bare: moved downstream
9340
9341     (void) CoordsToAlgebraic(boards[forwardMostMove],
9342                              PosFlags(forwardMostMove),
9343                              fromY, fromX, toY, toX, promoChar,
9344                              parseList[forwardMostMove]);
9345
9346     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9347         int timeLeft; static int lastLoadFlag=0; int king, piece;
9348         piece = boards[forwardMostMove][fromY][fromX];
9349         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9350         if(gameInfo.variant == VariantKnightmate)
9351             king += (int) WhiteUnicorn - (int) WhiteKing;
9352         if(forwardMostMove == 0) {
9353             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9354                 fprintf(serverMoves, "%s;", UserName());
9355             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9356                 fprintf(serverMoves, "%s;", second.tidy);
9357             fprintf(serverMoves, "%s;", first.tidy);
9358             if(gameMode == MachinePlaysWhite)
9359                 fprintf(serverMoves, "%s;", UserName());
9360             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9361                 fprintf(serverMoves, "%s;", second.tidy);
9362         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9363         lastLoadFlag = loadFlag;
9364         // print base move
9365         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9366         // print castling suffix
9367         if( toY == fromY && piece == king ) {
9368             if(toX-fromX > 1)
9369                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9370             if(fromX-toX >1)
9371                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9372         }
9373         // e.p. suffix
9374         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9375              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9376              boards[forwardMostMove][toY][toX] == EmptySquare
9377              && fromX != toX && fromY != toY)
9378                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9379         // promotion suffix
9380         if(promoChar != NULLCHAR)
9381                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9382         if(!loadFlag) {
9383                 char buf[MOVE_LEN*2], *p; int len;
9384             fprintf(serverMoves, "/%d/%d",
9385                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9386             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9387             else                      timeLeft = blackTimeRemaining/1000;
9388             fprintf(serverMoves, "/%d", timeLeft);
9389                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9390                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9391                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9392             fprintf(serverMoves, "/%s", buf);
9393         }
9394         fflush(serverMoves);
9395     }
9396
9397     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9398         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9399       return;
9400     }
9401     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9402     if (commentList[forwardMostMove+1] != NULL) {
9403         free(commentList[forwardMostMove+1]);
9404         commentList[forwardMostMove+1] = NULL;
9405     }
9406     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9407     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9408     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9409     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9410     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9411     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9412     adjustedClock = FALSE;
9413     gameInfo.result = GameUnfinished;
9414     if (gameInfo.resultDetails != NULL) {
9415         free(gameInfo.resultDetails);
9416         gameInfo.resultDetails = NULL;
9417     }
9418     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9419                               moveList[forwardMostMove - 1]);
9420     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9421       case MT_NONE:
9422       case MT_STALEMATE:
9423       default:
9424         break;
9425       case MT_CHECK:
9426         if(gameInfo.variant != VariantShogi)
9427             strcat(parseList[forwardMostMove - 1], "+");
9428         break;
9429       case MT_CHECKMATE:
9430       case MT_STAINMATE:
9431         strcat(parseList[forwardMostMove - 1], "#");
9432         break;
9433     }
9434     if (appData.debugMode) {
9435         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9436     }
9437
9438 }
9439
9440 /* Updates currentMove if not pausing */
9441 void
9442 ShowMove (int fromX, int fromY, int toX, int toY)
9443 {
9444     int instant = (gameMode == PlayFromGameFile) ?
9445         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9446     if(appData.noGUI) return;
9447     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9448         if (!instant) {
9449             if (forwardMostMove == currentMove + 1) {
9450                 AnimateMove(boards[forwardMostMove - 1],
9451                             fromX, fromY, toX, toY);
9452             }
9453             if (appData.highlightLastMove) {
9454                 SetHighlights(fromX, fromY, toX, toY);
9455             }
9456         }
9457         currentMove = forwardMostMove;
9458     }
9459
9460     if (instant) return;
9461
9462     DisplayMove(currentMove - 1);
9463     DrawPosition(FALSE, boards[currentMove]);
9464     DisplayBothClocks();
9465     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9466 }
9467
9468 void
9469 SendEgtPath (ChessProgramState *cps)
9470 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9471         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9472
9473         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9474
9475         while(*p) {
9476             char c, *q = name+1, *r, *s;
9477
9478             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9479             while(*p && *p != ',') *q++ = *p++;
9480             *q++ = ':'; *q = 0;
9481             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9482                 strcmp(name, ",nalimov:") == 0 ) {
9483                 // take nalimov path from the menu-changeable option first, if it is defined
9484               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9485                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9486             } else
9487             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9488                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9489                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9490                 s = r = StrStr(s, ":") + 1; // beginning of path info
9491                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9492                 c = *r; *r = 0;             // temporarily null-terminate path info
9493                     *--q = 0;               // strip of trailig ':' from name
9494                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9495                 *r = c;
9496                 SendToProgram(buf,cps);     // send egtbpath command for this format
9497             }
9498             if(*p == ',') p++; // read away comma to position for next format name
9499         }
9500 }
9501
9502 void
9503 InitChessProgram (ChessProgramState *cps, int setup)
9504 /* setup needed to setup FRC opening position */
9505 {
9506     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9507     if (appData.noChessProgram) return;
9508     hintRequested = FALSE;
9509     bookRequested = FALSE;
9510
9511     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9512     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9513     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9514     if(cps->memSize) { /* [HGM] memory */
9515       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9516         SendToProgram(buf, cps);
9517     }
9518     SendEgtPath(cps); /* [HGM] EGT */
9519     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9520       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9521         SendToProgram(buf, cps);
9522     }
9523
9524     SendToProgram(cps->initString, cps);
9525     if (gameInfo.variant != VariantNormal &&
9526         gameInfo.variant != VariantLoadable
9527         /* [HGM] also send variant if board size non-standard */
9528         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9529                                             ) {
9530       char *v = VariantName(gameInfo.variant);
9531       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9532         /* [HGM] in protocol 1 we have to assume all variants valid */
9533         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9534         DisplayFatalError(buf, 0, 1);
9535         return;
9536       }
9537
9538       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9539       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9540       if( gameInfo.variant == VariantXiangqi )
9541            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9542       if( gameInfo.variant == VariantShogi )
9543            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9544       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9545            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9546       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9547           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9548            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9549       if( gameInfo.variant == VariantCourier )
9550            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9551       if( gameInfo.variant == VariantSuper )
9552            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9553       if( gameInfo.variant == VariantGreat )
9554            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9555       if( gameInfo.variant == VariantSChess )
9556            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9557       if( gameInfo.variant == VariantGrand )
9558            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9559
9560       if(overruled) {
9561         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9562                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9563            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9564            if(StrStr(cps->variants, b) == NULL) {
9565                // specific sized variant not known, check if general sizing allowed
9566                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9567                    if(StrStr(cps->variants, "boardsize") == NULL) {
9568                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9569                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9570                        DisplayFatalError(buf, 0, 1);
9571                        return;
9572                    }
9573                    /* [HGM] here we really should compare with the maximum supported board size */
9574                }
9575            }
9576       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9577       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9578       SendToProgram(buf, cps);
9579     }
9580     currentlyInitializedVariant = gameInfo.variant;
9581
9582     /* [HGM] send opening position in FRC to first engine */
9583     if(setup) {
9584           SendToProgram("force\n", cps);
9585           SendBoard(cps, 0);
9586           /* engine is now in force mode! Set flag to wake it up after first move. */
9587           setboardSpoiledMachineBlack = 1;
9588     }
9589
9590     if (cps->sendICS) {
9591       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9592       SendToProgram(buf, cps);
9593     }
9594     cps->maybeThinking = FALSE;
9595     cps->offeredDraw = 0;
9596     if (!appData.icsActive) {
9597         SendTimeControl(cps, movesPerSession, timeControl,
9598                         timeIncrement, appData.searchDepth,
9599                         searchTime);
9600     }
9601     if (appData.showThinking
9602         // [HGM] thinking: four options require thinking output to be sent
9603         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9604                                 ) {
9605         SendToProgram("post\n", cps);
9606     }
9607     SendToProgram("hard\n", cps);
9608     if (!appData.ponderNextMove) {
9609         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9610            it without being sure what state we are in first.  "hard"
9611            is not a toggle, so that one is OK.
9612          */
9613         SendToProgram("easy\n", cps);
9614     }
9615     if (cps->usePing) {
9616       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9617       SendToProgram(buf, cps);
9618     }
9619     cps->initDone = TRUE;
9620     ClearEngineOutputPane(cps == &second);
9621 }
9622
9623
9624 void
9625 StartChessProgram (ChessProgramState *cps)
9626 {
9627     char buf[MSG_SIZ];
9628     int err;
9629
9630     if (appData.noChessProgram) return;
9631     cps->initDone = FALSE;
9632
9633     if (strcmp(cps->host, "localhost") == 0) {
9634         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9635     } else if (*appData.remoteShell == NULLCHAR) {
9636         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9637     } else {
9638         if (*appData.remoteUser == NULLCHAR) {
9639           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9640                     cps->program);
9641         } else {
9642           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9643                     cps->host, appData.remoteUser, cps->program);
9644         }
9645         err = StartChildProcess(buf, "", &cps->pr);
9646     }
9647
9648     if (err != 0) {
9649       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9650         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9651         if(cps != &first) return;
9652         appData.noChessProgram = TRUE;
9653         ThawUI();
9654         SetNCPMode();
9655 //      DisplayFatalError(buf, err, 1);
9656 //      cps->pr = NoProc;
9657 //      cps->isr = NULL;
9658         return;
9659     }
9660
9661     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9662     if (cps->protocolVersion > 1) {
9663       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9664       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9665       cps->comboCnt = 0;  //                and values of combo boxes
9666       SendToProgram(buf, cps);
9667     } else {
9668       SendToProgram("xboard\n", cps);
9669     }
9670 }
9671
9672 void
9673 TwoMachinesEventIfReady P((void))
9674 {
9675   static int curMess = 0;
9676   if (first.lastPing != first.lastPong) {
9677     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9678     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9679     return;
9680   }
9681   if (second.lastPing != second.lastPong) {
9682     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9683     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9684     return;
9685   }
9686   DisplayMessage("", ""); curMess = 0;
9687   ThawUI();
9688   TwoMachinesEvent();
9689 }
9690
9691 char *
9692 MakeName (char *template)
9693 {
9694     time_t clock;
9695     struct tm *tm;
9696     static char buf[MSG_SIZ];
9697     char *p = buf;
9698     int i;
9699
9700     clock = time((time_t *)NULL);
9701     tm = localtime(&clock);
9702
9703     while(*p++ = *template++) if(p[-1] == '%') {
9704         switch(*template++) {
9705           case 0:   *p = 0; return buf;
9706           case 'Y': i = tm->tm_year+1900; break;
9707           case 'y': i = tm->tm_year-100; break;
9708           case 'M': i = tm->tm_mon+1; break;
9709           case 'd': i = tm->tm_mday; break;
9710           case 'h': i = tm->tm_hour; break;
9711           case 'm': i = tm->tm_min; break;
9712           case 's': i = tm->tm_sec; break;
9713           default:  i = 0;
9714         }
9715         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9716     }
9717     return buf;
9718 }
9719
9720 int
9721 CountPlayers (char *p)
9722 {
9723     int n = 0;
9724     while(p = strchr(p, '\n')) p++, n++; // count participants
9725     return n;
9726 }
9727
9728 FILE *
9729 WriteTourneyFile (char *results, FILE *f)
9730 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9731     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9732     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9733         // create a file with tournament description
9734         fprintf(f, "-participants {%s}\n", appData.participants);
9735         fprintf(f, "-seedBase %d\n", appData.seedBase);
9736         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9737         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9738         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9739         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9740         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9741         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9742         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9743         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9744         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9745         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9746         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9747         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9748         if(searchTime > 0)
9749                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9750         else {
9751                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9752                 fprintf(f, "-tc %s\n", appData.timeControl);
9753                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9754         }
9755         fprintf(f, "-results \"%s\"\n", results);
9756     }
9757     return f;
9758 }
9759
9760 #define MAXENGINES 1000
9761 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9762
9763 void
9764 Substitute (char *participants, int expunge)
9765 {
9766     int i, changed, changes=0, nPlayers=0;
9767     char *p, *q, *r, buf[MSG_SIZ];
9768     if(participants == NULL) return;
9769     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9770     r = p = participants; q = appData.participants;
9771     while(*p && *p == *q) {
9772         if(*p == '\n') r = p+1, nPlayers++;
9773         p++; q++;
9774     }
9775     if(*p) { // difference
9776         while(*p && *p++ != '\n');
9777         while(*q && *q++ != '\n');
9778       changed = nPlayers;
9779         changes = 1 + (strcmp(p, q) != 0);
9780     }
9781     if(changes == 1) { // a single engine mnemonic was changed
9782         q = r; while(*q) nPlayers += (*q++ == '\n');
9783         p = buf; while(*r && (*p = *r++) != '\n') p++;
9784         *p = NULLCHAR;
9785         NamesToList(firstChessProgramNames, command, mnemonic);
9786         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9787         if(mnemonic[i]) { // The substitute is valid
9788             FILE *f;
9789             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9790                 flock(fileno(f), LOCK_EX);
9791                 ParseArgsFromFile(f);
9792                 fseek(f, 0, SEEK_SET);
9793                 FREE(appData.participants); appData.participants = participants;
9794                 if(expunge) { // erase results of replaced engine
9795                     int len = strlen(appData.results), w, b, dummy;
9796                     for(i=0; i<len; i++) {
9797                         Pairing(i, nPlayers, &w, &b, &dummy);
9798                         if((w == changed || b == changed) && appData.results[i] == '*') {
9799                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9800                             fclose(f);
9801                             return;
9802                         }
9803                     }
9804                     for(i=0; i<len; i++) {
9805                         Pairing(i, nPlayers, &w, &b, &dummy);
9806                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9807                     }
9808                 }
9809                 WriteTourneyFile(appData.results, f);
9810                 fclose(f); // release lock
9811                 return;
9812             }
9813         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9814     }
9815     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9816     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9817     free(participants);
9818     return;
9819 }
9820
9821 int
9822 CreateTourney (char *name)
9823 {
9824         FILE *f;
9825         if(matchMode && strcmp(name, appData.tourneyFile)) {
9826              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9827         }
9828         if(name[0] == NULLCHAR) {
9829             if(appData.participants[0])
9830                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9831             return 0;
9832         }
9833         f = fopen(name, "r");
9834         if(f) { // file exists
9835             ASSIGN(appData.tourneyFile, name);
9836             ParseArgsFromFile(f); // parse it
9837         } else {
9838             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9839             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9840                 DisplayError(_("Not enough participants"), 0);
9841                 return 0;
9842             }
9843             ASSIGN(appData.tourneyFile, name);
9844             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9845             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9846         }
9847         fclose(f);
9848         appData.noChessProgram = FALSE;
9849         appData.clockMode = TRUE;
9850         SetGNUMode();
9851         return 1;
9852 }
9853
9854 void
9855 NamesToList (char *names, char **engineList, char **engineMnemonic)
9856 {
9857     char buf[MSG_SIZ], *p, *q;
9858     int i=1;
9859     while(*names) {
9860         p = names; q = buf;
9861         while(*p && *p != '\n') *q++ = *p++;
9862         *q = 0;
9863         if(engineList[i]) free(engineList[i]);
9864         engineList[i] = strdup(buf);
9865         if(*p == '\n') p++;
9866         TidyProgramName(engineList[i], "localhost", buf);
9867         if(engineMnemonic[i]) free(engineMnemonic[i]);
9868         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9869             strcat(buf, " (");
9870             sscanf(q + 8, "%s", buf + strlen(buf));
9871             strcat(buf, ")");
9872         }
9873         engineMnemonic[i] = strdup(buf);
9874         names = p; i++;
9875       if(i > MAXENGINES - 2) break;
9876     }
9877     engineList[i] = engineMnemonic[i] = NULL;
9878 }
9879
9880 // following implemented as macro to avoid type limitations
9881 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9882
9883 void
9884 SwapEngines (int n)
9885 {   // swap settings for first engine and other engine (so far only some selected options)
9886     int h;
9887     char *p;
9888     if(n == 0) return;
9889     SWAP(directory, p)
9890     SWAP(chessProgram, p)
9891     SWAP(isUCI, h)
9892     SWAP(hasOwnBookUCI, h)
9893     SWAP(protocolVersion, h)
9894     SWAP(reuse, h)
9895     SWAP(scoreIsAbsolute, h)
9896     SWAP(timeOdds, h)
9897     SWAP(logo, p)
9898     SWAP(pgnName, p)
9899     SWAP(pvSAN, h)
9900     SWAP(engOptions, p)
9901 }
9902
9903 void
9904 SetPlayer (int player)
9905 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9906     int i;
9907     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9908     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9909     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9910     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9911     if(mnemonic[i]) {
9912         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9913         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9914         appData.firstHasOwnBookUCI = !appData.defNoBook;
9915         ParseArgsFromString(buf);
9916     }
9917     free(engineName);
9918 }
9919
9920 int
9921 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9922 {   // determine players from game number
9923     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9924
9925     if(appData.tourneyType == 0) {
9926         roundsPerCycle = (nPlayers - 1) | 1;
9927         pairingsPerRound = nPlayers / 2;
9928     } else if(appData.tourneyType > 0) {
9929         roundsPerCycle = nPlayers - appData.tourneyType;
9930         pairingsPerRound = appData.tourneyType;
9931     }
9932     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9933     gamesPerCycle = gamesPerRound * roundsPerCycle;
9934     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9935     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9936     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9937     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9938     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9939     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9940
9941     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9942     if(appData.roundSync) *syncInterval = gamesPerRound;
9943
9944     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9945
9946     if(appData.tourneyType == 0) {
9947         if(curPairing == (nPlayers-1)/2 ) {
9948             *whitePlayer = curRound;
9949             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9950         } else {
9951             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9952             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9953             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9954             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9955         }
9956     } else if(appData.tourneyType > 0) {
9957         *whitePlayer = curPairing;
9958         *blackPlayer = curRound + appData.tourneyType;
9959     }
9960
9961     // take care of white/black alternation per round. 
9962     // For cycles and games this is already taken care of by default, derived from matchGame!
9963     return curRound & 1;
9964 }
9965
9966 int
9967 NextTourneyGame (int nr, int *swapColors)
9968 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9969     char *p, *q;
9970     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9971     FILE *tf;
9972     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9973     tf = fopen(appData.tourneyFile, "r");
9974     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9975     ParseArgsFromFile(tf); fclose(tf);
9976     InitTimeControls(); // TC might be altered from tourney file
9977
9978     nPlayers = CountPlayers(appData.participants); // count participants
9979     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
9980     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9981
9982     if(syncInterval) {
9983         p = q = appData.results;
9984         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9985         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9986             DisplayMessage(_("Waiting for other game(s)"),"");
9987             waitingForGame = TRUE;
9988             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9989             return 0;
9990         }
9991         waitingForGame = FALSE;
9992     }
9993
9994     if(appData.tourneyType < 0) {
9995         if(nr>=0 && !pairingReceived) {
9996             char buf[1<<16];
9997             if(pairing.pr == NoProc) {
9998                 if(!appData.pairingEngine[0]) {
9999                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10000                     return 0;
10001                 }
10002                 StartChessProgram(&pairing); // starts the pairing engine
10003             }
10004             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10005             SendToProgram(buf, &pairing);
10006             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10007             SendToProgram(buf, &pairing);
10008             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10009         }
10010         pairingReceived = 0;                              // ... so we continue here 
10011         *swapColors = 0;
10012         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10013         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10014         matchGame = 1; roundNr = nr / syncInterval + 1;
10015     }
10016
10017     if(first.pr != NoProc || second.pr != NoProc) return 1; // engines already loaded
10018
10019     // redefine engines, engine dir, etc.
10020     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10021     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
10022     SwapEngines(1);
10023     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
10024     SwapEngines(1);         // and make that valid for second engine by swapping
10025     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10026     InitEngine(&second, 1);
10027     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10028     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10029     return 1;
10030 }
10031
10032 void
10033 NextMatchGame ()
10034 {   // performs game initialization that does not invoke engines, and then tries to start the game
10035     int res, firstWhite, swapColors = 0;
10036     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10037     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10038     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10039     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10040     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10041     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10042     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10043     Reset(FALSE, first.pr != NoProc);
10044     res = LoadGameOrPosition(matchGame); // setup game
10045     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10046     if(!res) return; // abort when bad game/pos file
10047     TwoMachinesEvent();
10048 }
10049
10050 void
10051 UserAdjudicationEvent (int result)
10052 {
10053     ChessMove gameResult = GameIsDrawn;
10054
10055     if( result > 0 ) {
10056         gameResult = WhiteWins;
10057     }
10058     else if( result < 0 ) {
10059         gameResult = BlackWins;
10060     }
10061
10062     if( gameMode == TwoMachinesPlay ) {
10063         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10064     }
10065 }
10066
10067
10068 // [HGM] save: calculate checksum of game to make games easily identifiable
10069 int
10070 StringCheckSum (char *s)
10071 {
10072         int i = 0;
10073         if(s==NULL) return 0;
10074         while(*s) i = i*259 + *s++;
10075         return i;
10076 }
10077
10078 int
10079 GameCheckSum ()
10080 {
10081         int i, sum=0;
10082         for(i=backwardMostMove; i<forwardMostMove; i++) {
10083                 sum += pvInfoList[i].depth;
10084                 sum += StringCheckSum(parseList[i]);
10085                 sum += StringCheckSum(commentList[i]);
10086                 sum *= 261;
10087         }
10088         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10089         return sum + StringCheckSum(commentList[i]);
10090 } // end of save patch
10091
10092 void
10093 GameEnds (ChessMove result, char *resultDetails, int whosays)
10094 {
10095     GameMode nextGameMode;
10096     int isIcsGame;
10097     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10098
10099     if(endingGame) return; /* [HGM] crash: forbid recursion */
10100     endingGame = 1;
10101     if(twoBoards) { // [HGM] dual: switch back to one board
10102         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10103         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10104     }
10105     if (appData.debugMode) {
10106       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10107               result, resultDetails ? resultDetails : "(null)", whosays);
10108     }
10109
10110     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10111
10112     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10113         /* If we are playing on ICS, the server decides when the
10114            game is over, but the engine can offer to draw, claim
10115            a draw, or resign.
10116          */
10117 #if ZIPPY
10118         if (appData.zippyPlay && first.initDone) {
10119             if (result == GameIsDrawn) {
10120                 /* In case draw still needs to be claimed */
10121                 SendToICS(ics_prefix);
10122                 SendToICS("draw\n");
10123             } else if (StrCaseStr(resultDetails, "resign")) {
10124                 SendToICS(ics_prefix);
10125                 SendToICS("resign\n");
10126             }
10127         }
10128 #endif
10129         endingGame = 0; /* [HGM] crash */
10130         return;
10131     }
10132
10133     /* If we're loading the game from a file, stop */
10134     if (whosays == GE_FILE) {
10135       (void) StopLoadGameTimer();
10136       gameFileFP = NULL;
10137     }
10138
10139     /* Cancel draw offers */
10140     first.offeredDraw = second.offeredDraw = 0;
10141
10142     /* If this is an ICS game, only ICS can really say it's done;
10143        if not, anyone can. */
10144     isIcsGame = (gameMode == IcsPlayingWhite ||
10145                  gameMode == IcsPlayingBlack ||
10146                  gameMode == IcsObserving    ||
10147                  gameMode == IcsExamining);
10148
10149     if (!isIcsGame || whosays == GE_ICS) {
10150         /* OK -- not an ICS game, or ICS said it was done */
10151         StopClocks();
10152         if (!isIcsGame && !appData.noChessProgram)
10153           SetUserThinkingEnables();
10154
10155         /* [HGM] if a machine claims the game end we verify this claim */
10156         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10157             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10158                 char claimer;
10159                 ChessMove trueResult = (ChessMove) -1;
10160
10161                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10162                                             first.twoMachinesColor[0] :
10163                                             second.twoMachinesColor[0] ;
10164
10165                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10166                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10167                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10168                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10169                 } else
10170                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10171                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10172                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10173                 } else
10174                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10175                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10176                 }
10177
10178                 // now verify win claims, but not in drop games, as we don't understand those yet
10179                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10180                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10181                     (result == WhiteWins && claimer == 'w' ||
10182                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10183                       if (appData.debugMode) {
10184                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10185                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10186                       }
10187                       if(result != trueResult) {
10188                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10189                               result = claimer == 'w' ? BlackWins : WhiteWins;
10190                               resultDetails = buf;
10191                       }
10192                 } else
10193                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10194                     && (forwardMostMove <= backwardMostMove ||
10195                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10196                         (claimer=='b')==(forwardMostMove&1))
10197                                                                                   ) {
10198                       /* [HGM] verify: draws that were not flagged are false claims */
10199                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10200                       result = claimer == 'w' ? BlackWins : WhiteWins;
10201                       resultDetails = buf;
10202                 }
10203                 /* (Claiming a loss is accepted no questions asked!) */
10204             }
10205             /* [HGM] bare: don't allow bare King to win */
10206             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10207                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10208                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10209                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10210                && result != GameIsDrawn)
10211             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10212                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10213                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10214                         if(p >= 0 && p <= (int)WhiteKing) k++;
10215                 }
10216                 if (appData.debugMode) {
10217                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10218                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10219                 }
10220                 if(k <= 1) {
10221                         result = GameIsDrawn;
10222                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10223                         resultDetails = buf;
10224                 }
10225             }
10226         }
10227
10228
10229         if(serverMoves != NULL && !loadFlag) { char c = '=';
10230             if(result==WhiteWins) c = '+';
10231             if(result==BlackWins) c = '-';
10232             if(resultDetails != NULL)
10233                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10234         }
10235         if (resultDetails != NULL) {
10236             gameInfo.result = result;
10237             gameInfo.resultDetails = StrSave(resultDetails);
10238
10239             /* display last move only if game was not loaded from file */
10240             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10241                 DisplayMove(currentMove - 1);
10242
10243             if (forwardMostMove != 0) {
10244                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10245                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10246                                                                 ) {
10247                     if (*appData.saveGameFile != NULLCHAR) {
10248                         SaveGameToFile(appData.saveGameFile, TRUE);
10249                     } else if (appData.autoSaveGames) {
10250                         AutoSaveGame();
10251                     }
10252                     if (*appData.savePositionFile != NULLCHAR) {
10253                         SavePositionToFile(appData.savePositionFile);
10254                     }
10255                 }
10256             }
10257
10258             /* Tell program how game ended in case it is learning */
10259             /* [HGM] Moved this to after saving the PGN, just in case */
10260             /* engine died and we got here through time loss. In that */
10261             /* case we will get a fatal error writing the pipe, which */
10262             /* would otherwise lose us the PGN.                       */
10263             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10264             /* output during GameEnds should never be fatal anymore   */
10265             if (gameMode == MachinePlaysWhite ||
10266                 gameMode == MachinePlaysBlack ||
10267                 gameMode == TwoMachinesPlay ||
10268                 gameMode == IcsPlayingWhite ||
10269                 gameMode == IcsPlayingBlack ||
10270                 gameMode == BeginningOfGame) {
10271                 char buf[MSG_SIZ];
10272                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10273                         resultDetails);
10274                 if (first.pr != NoProc) {
10275                     SendToProgram(buf, &first);
10276                 }
10277                 if (second.pr != NoProc &&
10278                     gameMode == TwoMachinesPlay) {
10279                     SendToProgram(buf, &second);
10280                 }
10281             }
10282         }
10283
10284         if (appData.icsActive) {
10285             if (appData.quietPlay &&
10286                 (gameMode == IcsPlayingWhite ||
10287                  gameMode == IcsPlayingBlack)) {
10288                 SendToICS(ics_prefix);
10289                 SendToICS("set shout 1\n");
10290             }
10291             nextGameMode = IcsIdle;
10292             ics_user_moved = FALSE;
10293             /* clean up premove.  It's ugly when the game has ended and the
10294              * premove highlights are still on the board.
10295              */
10296             if (gotPremove) {
10297               gotPremove = FALSE;
10298               ClearPremoveHighlights();
10299               DrawPosition(FALSE, boards[currentMove]);
10300             }
10301             if (whosays == GE_ICS) {
10302                 switch (result) {
10303                 case WhiteWins:
10304                     if (gameMode == IcsPlayingWhite)
10305                         PlayIcsWinSound();
10306                     else if(gameMode == IcsPlayingBlack)
10307                         PlayIcsLossSound();
10308                     break;
10309                 case BlackWins:
10310                     if (gameMode == IcsPlayingBlack)
10311                         PlayIcsWinSound();
10312                     else if(gameMode == IcsPlayingWhite)
10313                         PlayIcsLossSound();
10314                     break;
10315                 case GameIsDrawn:
10316                     PlayIcsDrawSound();
10317                     break;
10318                 default:
10319                     PlayIcsUnfinishedSound();
10320                 }
10321             }
10322         } else if (gameMode == EditGame ||
10323                    gameMode == PlayFromGameFile ||
10324                    gameMode == AnalyzeMode ||
10325                    gameMode == AnalyzeFile) {
10326             nextGameMode = gameMode;
10327         } else {
10328             nextGameMode = EndOfGame;
10329         }
10330         pausing = FALSE;
10331         ModeHighlight();
10332     } else {
10333         nextGameMode = gameMode;
10334     }
10335
10336     if (appData.noChessProgram) {
10337         gameMode = nextGameMode;
10338         ModeHighlight();
10339         endingGame = 0; /* [HGM] crash */
10340         return;
10341     }
10342
10343     if (first.reuse) {
10344         /* Put first chess program into idle state */
10345         if (first.pr != NoProc &&
10346             (gameMode == MachinePlaysWhite ||
10347              gameMode == MachinePlaysBlack ||
10348              gameMode == TwoMachinesPlay ||
10349              gameMode == IcsPlayingWhite ||
10350              gameMode == IcsPlayingBlack ||
10351              gameMode == BeginningOfGame)) {
10352             SendToProgram("force\n", &first);
10353             if (first.usePing) {
10354               char buf[MSG_SIZ];
10355               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10356               SendToProgram(buf, &first);
10357             }
10358         }
10359     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10360         /* Kill off first chess program */
10361         if (first.isr != NULL)
10362           RemoveInputSource(first.isr);
10363         first.isr = NULL;
10364
10365         if (first.pr != NoProc) {
10366             ExitAnalyzeMode();
10367             DoSleep( appData.delayBeforeQuit );
10368             SendToProgram("quit\n", &first);
10369             DoSleep( appData.delayAfterQuit );
10370             DestroyChildProcess(first.pr, first.useSigterm);
10371         }
10372         first.pr = NoProc;
10373     }
10374     if (second.reuse) {
10375         /* Put second chess program into idle state */
10376         if (second.pr != NoProc &&
10377             gameMode == TwoMachinesPlay) {
10378             SendToProgram("force\n", &second);
10379             if (second.usePing) {
10380               char buf[MSG_SIZ];
10381               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10382               SendToProgram(buf, &second);
10383             }
10384         }
10385     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10386         /* Kill off second chess program */
10387         if (second.isr != NULL)
10388           RemoveInputSource(second.isr);
10389         second.isr = NULL;
10390
10391         if (second.pr != NoProc) {
10392             DoSleep( appData.delayBeforeQuit );
10393             SendToProgram("quit\n", &second);
10394             DoSleep( appData.delayAfterQuit );
10395             DestroyChildProcess(second.pr, second.useSigterm);
10396         }
10397         second.pr = NoProc;
10398     }
10399
10400     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10401         char resChar = '=';
10402         switch (result) {
10403         case WhiteWins:
10404           resChar = '+';
10405           if (first.twoMachinesColor[0] == 'w') {
10406             first.matchWins++;
10407           } else {
10408             second.matchWins++;
10409           }
10410           break;
10411         case BlackWins:
10412           resChar = '-';
10413           if (first.twoMachinesColor[0] == 'b') {
10414             first.matchWins++;
10415           } else {
10416             second.matchWins++;
10417           }
10418           break;
10419         case GameUnfinished:
10420           resChar = ' ';
10421         default:
10422           break;
10423         }
10424
10425         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10426         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10427             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10428             ReserveGame(nextGame, resChar); // sets nextGame
10429             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10430             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10431         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10432
10433         if (nextGame <= appData.matchGames && !abortMatch) {
10434             gameMode = nextGameMode;
10435             matchGame = nextGame; // this will be overruled in tourney mode!
10436             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10437             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10438             endingGame = 0; /* [HGM] crash */
10439             return;
10440         } else {
10441             gameMode = nextGameMode;
10442             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10443                      first.tidy, second.tidy,
10444                      first.matchWins, second.matchWins,
10445                      appData.matchGames - (first.matchWins + second.matchWins));
10446             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10447             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10448             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10449             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10450                 first.twoMachinesColor = "black\n";
10451                 second.twoMachinesColor = "white\n";
10452             } else {
10453                 first.twoMachinesColor = "white\n";
10454                 second.twoMachinesColor = "black\n";
10455             }
10456         }
10457     }
10458     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10459         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10460       ExitAnalyzeMode();
10461     gameMode = nextGameMode;
10462     ModeHighlight();
10463     endingGame = 0;  /* [HGM] crash */
10464     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10465         if(matchMode == TRUE) { // match through command line: exit with or without popup
10466             if(ranking) {
10467                 ToNrEvent(forwardMostMove);
10468                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10469                 else ExitEvent(0);
10470             } else DisplayFatalError(buf, 0, 0);
10471         } else { // match through menu; just stop, with or without popup
10472             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10473             ModeHighlight();
10474             if(ranking){
10475                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10476             } else DisplayNote(buf);
10477       }
10478       if(ranking) free(ranking);
10479     }
10480 }
10481
10482 /* Assumes program was just initialized (initString sent).
10483    Leaves program in force mode. */
10484 void
10485 FeedMovesToProgram (ChessProgramState *cps, int upto)
10486 {
10487     int i;
10488
10489     if (appData.debugMode)
10490       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10491               startedFromSetupPosition ? "position and " : "",
10492               backwardMostMove, upto, cps->which);
10493     if(currentlyInitializedVariant != gameInfo.variant) {
10494       char buf[MSG_SIZ];
10495         // [HGM] variantswitch: make engine aware of new variant
10496         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10497                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10498         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10499         SendToProgram(buf, cps);
10500         currentlyInitializedVariant = gameInfo.variant;
10501     }
10502     SendToProgram("force\n", cps);
10503     if (startedFromSetupPosition) {
10504         SendBoard(cps, backwardMostMove);
10505     if (appData.debugMode) {
10506         fprintf(debugFP, "feedMoves\n");
10507     }
10508     }
10509     for (i = backwardMostMove; i < upto; i++) {
10510         SendMoveToProgram(i, cps);
10511     }
10512 }
10513
10514
10515 int
10516 ResurrectChessProgram ()
10517 {
10518      /* The chess program may have exited.
10519         If so, restart it and feed it all the moves made so far. */
10520     static int doInit = 0;
10521
10522     if (appData.noChessProgram) return 1;
10523
10524     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10525         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10526         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10527         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10528     } else {
10529         if (first.pr != NoProc) return 1;
10530         StartChessProgram(&first);
10531     }
10532     InitChessProgram(&first, FALSE);
10533     FeedMovesToProgram(&first, currentMove);
10534
10535     if (!first.sendTime) {
10536         /* can't tell gnuchess what its clock should read,
10537            so we bow to its notion. */
10538         ResetClocks();
10539         timeRemaining[0][currentMove] = whiteTimeRemaining;
10540         timeRemaining[1][currentMove] = blackTimeRemaining;
10541     }
10542
10543     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10544                 appData.icsEngineAnalyze) && first.analysisSupport) {
10545       SendToProgram("analyze\n", &first);
10546       first.analyzing = TRUE;
10547     }
10548     return 1;
10549 }
10550
10551 /*
10552  * Button procedures
10553  */
10554 void
10555 Reset (int redraw, int init)
10556 {
10557     int i;
10558
10559     if (appData.debugMode) {
10560         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10561                 redraw, init, gameMode);
10562     }
10563     CleanupTail(); // [HGM] vari: delete any stored variations
10564     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10565     pausing = pauseExamInvalid = FALSE;
10566     startedFromSetupPosition = blackPlaysFirst = FALSE;
10567     firstMove = TRUE;
10568     whiteFlag = blackFlag = FALSE;
10569     userOfferedDraw = FALSE;
10570     hintRequested = bookRequested = FALSE;
10571     first.maybeThinking = FALSE;
10572     second.maybeThinking = FALSE;
10573     first.bookSuspend = FALSE; // [HGM] book
10574     second.bookSuspend = FALSE;
10575     thinkOutput[0] = NULLCHAR;
10576     lastHint[0] = NULLCHAR;
10577     ClearGameInfo(&gameInfo);
10578     gameInfo.variant = StringToVariant(appData.variant);
10579     ics_user_moved = ics_clock_paused = FALSE;
10580     ics_getting_history = H_FALSE;
10581     ics_gamenum = -1;
10582     white_holding[0] = black_holding[0] = NULLCHAR;
10583     ClearProgramStats();
10584     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10585
10586     ResetFrontEnd();
10587     ClearHighlights();
10588     flipView = appData.flipView;
10589     ClearPremoveHighlights();
10590     gotPremove = FALSE;
10591     alarmSounded = FALSE;
10592
10593     GameEnds(EndOfFile, NULL, GE_PLAYER);
10594     if(appData.serverMovesName != NULL) {
10595         /* [HGM] prepare to make moves file for broadcasting */
10596         clock_t t = clock();
10597         if(serverMoves != NULL) fclose(serverMoves);
10598         serverMoves = fopen(appData.serverMovesName, "r");
10599         if(serverMoves != NULL) {
10600             fclose(serverMoves);
10601             /* delay 15 sec before overwriting, so all clients can see end */
10602             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10603         }
10604         serverMoves = fopen(appData.serverMovesName, "w");
10605     }
10606
10607     ExitAnalyzeMode();
10608     gameMode = BeginningOfGame;
10609     ModeHighlight();
10610     if(appData.icsActive) gameInfo.variant = VariantNormal;
10611     currentMove = forwardMostMove = backwardMostMove = 0;
10612     MarkTargetSquares(1);
10613     InitPosition(redraw);
10614     for (i = 0; i < MAX_MOVES; i++) {
10615         if (commentList[i] != NULL) {
10616             free(commentList[i]);
10617             commentList[i] = NULL;
10618         }
10619     }
10620     ResetClocks();
10621     timeRemaining[0][0] = whiteTimeRemaining;
10622     timeRemaining[1][0] = blackTimeRemaining;
10623
10624     if (first.pr == NoProc) {
10625         StartChessProgram(&first);
10626     }
10627     if (init) {
10628             InitChessProgram(&first, startedFromSetupPosition);
10629     }
10630     DisplayTitle("");
10631     DisplayMessage("", "");
10632     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10633     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10634 }
10635
10636 void
10637 AutoPlayGameLoop ()
10638 {
10639     for (;;) {
10640         if (!AutoPlayOneMove())
10641           return;
10642         if (matchMode || appData.timeDelay == 0)
10643           continue;
10644         if (appData.timeDelay < 0)
10645           return;
10646         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10647         break;
10648     }
10649 }
10650
10651
10652 int
10653 AutoPlayOneMove ()
10654 {
10655     int fromX, fromY, toX, toY;
10656
10657     if (appData.debugMode) {
10658       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10659     }
10660
10661     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10662       return FALSE;
10663
10664     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10665       pvInfoList[currentMove].depth = programStats.depth;
10666       pvInfoList[currentMove].score = programStats.score;
10667       pvInfoList[currentMove].time  = 0;
10668       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10669     }
10670
10671     if (currentMove >= forwardMostMove) {
10672       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10673 //      gameMode = EndOfGame;
10674 //      ModeHighlight();
10675
10676       /* [AS] Clear current move marker at the end of a game */
10677       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10678
10679       return FALSE;
10680     }
10681
10682     toX = moveList[currentMove][2] - AAA;
10683     toY = moveList[currentMove][3] - ONE;
10684
10685     if (moveList[currentMove][1] == '@') {
10686         if (appData.highlightLastMove) {
10687             SetHighlights(-1, -1, toX, toY);
10688         }
10689     } else {
10690         fromX = moveList[currentMove][0] - AAA;
10691         fromY = moveList[currentMove][1] - ONE;
10692
10693         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10694
10695         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10696
10697         if (appData.highlightLastMove) {
10698             SetHighlights(fromX, fromY, toX, toY);
10699         }
10700     }
10701     DisplayMove(currentMove);
10702     SendMoveToProgram(currentMove++, &first);
10703     DisplayBothClocks();
10704     DrawPosition(FALSE, boards[currentMove]);
10705     // [HGM] PV info: always display, routine tests if empty
10706     DisplayComment(currentMove - 1, commentList[currentMove]);
10707     return TRUE;
10708 }
10709
10710
10711 int
10712 LoadGameOneMove (ChessMove readAhead)
10713 {
10714     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10715     char promoChar = NULLCHAR;
10716     ChessMove moveType;
10717     char move[MSG_SIZ];
10718     char *p, *q;
10719
10720     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10721         gameMode != AnalyzeMode && gameMode != Training) {
10722         gameFileFP = NULL;
10723         return FALSE;
10724     }
10725
10726     yyboardindex = forwardMostMove;
10727     if (readAhead != EndOfFile) {
10728       moveType = readAhead;
10729     } else {
10730       if (gameFileFP == NULL)
10731           return FALSE;
10732       moveType = (ChessMove) Myylex();
10733     }
10734
10735     done = FALSE;
10736     switch (moveType) {
10737       case Comment:
10738         if (appData.debugMode)
10739           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10740         p = yy_text;
10741
10742         /* append the comment but don't display it */
10743         AppendComment(currentMove, p, FALSE);
10744         return TRUE;
10745
10746       case WhiteCapturesEnPassant:
10747       case BlackCapturesEnPassant:
10748       case WhitePromotion:
10749       case BlackPromotion:
10750       case WhiteNonPromotion:
10751       case BlackNonPromotion:
10752       case NormalMove:
10753       case WhiteKingSideCastle:
10754       case WhiteQueenSideCastle:
10755       case BlackKingSideCastle:
10756       case BlackQueenSideCastle:
10757       case WhiteKingSideCastleWild:
10758       case WhiteQueenSideCastleWild:
10759       case BlackKingSideCastleWild:
10760       case BlackQueenSideCastleWild:
10761       /* PUSH Fabien */
10762       case WhiteHSideCastleFR:
10763       case WhiteASideCastleFR:
10764       case BlackHSideCastleFR:
10765       case BlackASideCastleFR:
10766       /* POP Fabien */
10767         if (appData.debugMode)
10768           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10769         fromX = currentMoveString[0] - AAA;
10770         fromY = currentMoveString[1] - ONE;
10771         toX = currentMoveString[2] - AAA;
10772         toY = currentMoveString[3] - ONE;
10773         promoChar = currentMoveString[4];
10774         break;
10775
10776       case WhiteDrop:
10777       case BlackDrop:
10778         if (appData.debugMode)
10779           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10780         fromX = moveType == WhiteDrop ?
10781           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10782         (int) CharToPiece(ToLower(currentMoveString[0]));
10783         fromY = DROP_RANK;
10784         toX = currentMoveString[2] - AAA;
10785         toY = currentMoveString[3] - ONE;
10786         break;
10787
10788       case WhiteWins:
10789       case BlackWins:
10790       case GameIsDrawn:
10791       case GameUnfinished:
10792         if (appData.debugMode)
10793           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10794         p = strchr(yy_text, '{');
10795         if (p == NULL) p = strchr(yy_text, '(');
10796         if (p == NULL) {
10797             p = yy_text;
10798             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10799         } else {
10800             q = strchr(p, *p == '{' ? '}' : ')');
10801             if (q != NULL) *q = NULLCHAR;
10802             p++;
10803         }
10804         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10805         GameEnds(moveType, p, GE_FILE);
10806         done = TRUE;
10807         if (cmailMsgLoaded) {
10808             ClearHighlights();
10809             flipView = WhiteOnMove(currentMove);
10810             if (moveType == GameUnfinished) flipView = !flipView;
10811             if (appData.debugMode)
10812               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10813         }
10814         break;
10815
10816       case EndOfFile:
10817         if (appData.debugMode)
10818           fprintf(debugFP, "Parser hit end of file\n");
10819         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10820           case MT_NONE:
10821           case MT_CHECK:
10822             break;
10823           case MT_CHECKMATE:
10824           case MT_STAINMATE:
10825             if (WhiteOnMove(currentMove)) {
10826                 GameEnds(BlackWins, "Black mates", GE_FILE);
10827             } else {
10828                 GameEnds(WhiteWins, "White mates", GE_FILE);
10829             }
10830             break;
10831           case MT_STALEMATE:
10832             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10833             break;
10834         }
10835         done = TRUE;
10836         break;
10837
10838       case MoveNumberOne:
10839         if (lastLoadGameStart == GNUChessGame) {
10840             /* GNUChessGames have numbers, but they aren't move numbers */
10841             if (appData.debugMode)
10842               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10843                       yy_text, (int) moveType);
10844             return LoadGameOneMove(EndOfFile); /* tail recursion */
10845         }
10846         /* else fall thru */
10847
10848       case XBoardGame:
10849       case GNUChessGame:
10850       case PGNTag:
10851         /* Reached start of next game in file */
10852         if (appData.debugMode)
10853           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10854         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10855           case MT_NONE:
10856           case MT_CHECK:
10857             break;
10858           case MT_CHECKMATE:
10859           case MT_STAINMATE:
10860             if (WhiteOnMove(currentMove)) {
10861                 GameEnds(BlackWins, "Black mates", GE_FILE);
10862             } else {
10863                 GameEnds(WhiteWins, "White mates", GE_FILE);
10864             }
10865             break;
10866           case MT_STALEMATE:
10867             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10868             break;
10869         }
10870         done = TRUE;
10871         break;
10872
10873       case PositionDiagram:     /* should not happen; ignore */
10874       case ElapsedTime:         /* ignore */
10875       case NAG:                 /* ignore */
10876         if (appData.debugMode)
10877           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10878                   yy_text, (int) moveType);
10879         return LoadGameOneMove(EndOfFile); /* tail recursion */
10880
10881       case IllegalMove:
10882         if (appData.testLegality) {
10883             if (appData.debugMode)
10884               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10885             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10886                     (forwardMostMove / 2) + 1,
10887                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10888             DisplayError(move, 0);
10889             done = TRUE;
10890         } else {
10891             if (appData.debugMode)
10892               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10893                       yy_text, currentMoveString);
10894             fromX = currentMoveString[0] - AAA;
10895             fromY = currentMoveString[1] - ONE;
10896             toX = currentMoveString[2] - AAA;
10897             toY = currentMoveString[3] - ONE;
10898             promoChar = currentMoveString[4];
10899         }
10900         break;
10901
10902       case AmbiguousMove:
10903         if (appData.debugMode)
10904           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10905         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10906                 (forwardMostMove / 2) + 1,
10907                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10908         DisplayError(move, 0);
10909         done = TRUE;
10910         break;
10911
10912       default:
10913       case ImpossibleMove:
10914         if (appData.debugMode)
10915           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10916         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10917                 (forwardMostMove / 2) + 1,
10918                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10919         DisplayError(move, 0);
10920         done = TRUE;
10921         break;
10922     }
10923
10924     if (done) {
10925         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10926             DrawPosition(FALSE, boards[currentMove]);
10927             DisplayBothClocks();
10928             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10929               DisplayComment(currentMove - 1, commentList[currentMove]);
10930         }
10931         (void) StopLoadGameTimer();
10932         gameFileFP = NULL;
10933         cmailOldMove = forwardMostMove;
10934         return FALSE;
10935     } else {
10936         /* currentMoveString is set as a side-effect of yylex */
10937
10938         thinkOutput[0] = NULLCHAR;
10939         MakeMove(fromX, fromY, toX, toY, promoChar);
10940         currentMove = forwardMostMove;
10941         return TRUE;
10942     }
10943 }
10944
10945 /* Load the nth game from the given file */
10946 int
10947 LoadGameFromFile (char *filename, int n, char *title, int useList)
10948 {
10949     FILE *f;
10950     char buf[MSG_SIZ];
10951
10952     if (strcmp(filename, "-") == 0) {
10953         f = stdin;
10954         title = "stdin";
10955     } else {
10956         f = fopen(filename, "rb");
10957         if (f == NULL) {
10958           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10959             DisplayError(buf, errno);
10960             return FALSE;
10961         }
10962     }
10963     if (fseek(f, 0, 0) == -1) {
10964         /* f is not seekable; probably a pipe */
10965         useList = FALSE;
10966     }
10967     if (useList && n == 0) {
10968         int error = GameListBuild(f);
10969         if (error) {
10970             DisplayError(_("Cannot build game list"), error);
10971         } else if (!ListEmpty(&gameList) &&
10972                    ((ListGame *) gameList.tailPred)->number > 1) {
10973             GameListPopUp(f, title);
10974             return TRUE;
10975         }
10976         GameListDestroy();
10977         n = 1;
10978     }
10979     if (n == 0) n = 1;
10980     return LoadGame(f, n, title, FALSE);
10981 }
10982
10983
10984 void
10985 MakeRegisteredMove ()
10986 {
10987     int fromX, fromY, toX, toY;
10988     char promoChar;
10989     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10990         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10991           case CMAIL_MOVE:
10992           case CMAIL_DRAW:
10993             if (appData.debugMode)
10994               fprintf(debugFP, "Restoring %s for game %d\n",
10995                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10996
10997             thinkOutput[0] = NULLCHAR;
10998             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10999             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11000             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11001             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11002             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11003             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11004             MakeMove(fromX, fromY, toX, toY, promoChar);
11005             ShowMove(fromX, fromY, toX, toY);
11006
11007             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11008               case MT_NONE:
11009               case MT_CHECK:
11010                 break;
11011
11012               case MT_CHECKMATE:
11013               case MT_STAINMATE:
11014                 if (WhiteOnMove(currentMove)) {
11015                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11016                 } else {
11017                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11018                 }
11019                 break;
11020
11021               case MT_STALEMATE:
11022                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11023                 break;
11024             }
11025
11026             break;
11027
11028           case CMAIL_RESIGN:
11029             if (WhiteOnMove(currentMove)) {
11030                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11031             } else {
11032                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11033             }
11034             break;
11035
11036           case CMAIL_ACCEPT:
11037             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11038             break;
11039
11040           default:
11041             break;
11042         }
11043     }
11044
11045     return;
11046 }
11047
11048 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11049 int
11050 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11051 {
11052     int retVal;
11053
11054     if (gameNumber > nCmailGames) {
11055         DisplayError(_("No more games in this message"), 0);
11056         return FALSE;
11057     }
11058     if (f == lastLoadGameFP) {
11059         int offset = gameNumber - lastLoadGameNumber;
11060         if (offset == 0) {
11061             cmailMsg[0] = NULLCHAR;
11062             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11063                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11064                 nCmailMovesRegistered--;
11065             }
11066             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11067             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11068                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11069             }
11070         } else {
11071             if (! RegisterMove()) return FALSE;
11072         }
11073     }
11074
11075     retVal = LoadGame(f, gameNumber, title, useList);
11076
11077     /* Make move registered during previous look at this game, if any */
11078     MakeRegisteredMove();
11079
11080     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11081         commentList[currentMove]
11082           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11083         DisplayComment(currentMove - 1, commentList[currentMove]);
11084     }
11085
11086     return retVal;
11087 }
11088
11089 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11090 int
11091 ReloadGame (int offset)
11092 {
11093     int gameNumber = lastLoadGameNumber + offset;
11094     if (lastLoadGameFP == NULL) {
11095         DisplayError(_("No game has been loaded yet"), 0);
11096         return FALSE;
11097     }
11098     if (gameNumber <= 0) {
11099         DisplayError(_("Can't back up any further"), 0);
11100         return FALSE;
11101     }
11102     if (cmailMsgLoaded) {
11103         return CmailLoadGame(lastLoadGameFP, gameNumber,
11104                              lastLoadGameTitle, lastLoadGameUseList);
11105     } else {
11106         return LoadGame(lastLoadGameFP, gameNumber,
11107                         lastLoadGameTitle, lastLoadGameUseList);
11108     }
11109 }
11110
11111 int keys[EmptySquare+1];
11112
11113 int
11114 PositionMatches (Board b1, Board b2)
11115 {
11116     int r, f, sum=0;
11117     switch(appData.searchMode) {
11118         case 1: return CompareWithRights(b1, b2);
11119         case 2:
11120             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11121                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11122             }
11123             return TRUE;
11124         case 3:
11125             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11126               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11127                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11128             }
11129             return sum==0;
11130         case 4:
11131             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11132                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11133             }
11134             return sum==0;
11135     }
11136     return TRUE;
11137 }
11138
11139 #define Q_PROMO  4
11140 #define Q_EP     3
11141 #define Q_BCASTL 2
11142 #define Q_WCASTL 1
11143
11144 int pieceList[256], quickBoard[256];
11145 ChessSquare pieceType[256] = { EmptySquare };
11146 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11147 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11148 int soughtTotal, turn;
11149 Boolean epOK, flipSearch;
11150
11151 typedef struct {
11152     unsigned char piece, to;
11153 } Move;
11154
11155 #define DSIZE (250000)
11156
11157 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11158 Move *moveDatabase = initialSpace;
11159 unsigned int movePtr, dataSize = DSIZE;
11160
11161 int
11162 MakePieceList (Board board, int *counts)
11163 {
11164     int r, f, n=Q_PROMO, total=0;
11165     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11166     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11167         int sq = f + (r<<4);
11168         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11169             quickBoard[sq] = ++n;
11170             pieceList[n] = sq;
11171             pieceType[n] = board[r][f];
11172             counts[board[r][f]]++;
11173             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11174             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11175             total++;
11176         }
11177     }
11178     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11179     return total;
11180 }
11181
11182 void
11183 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11184 {
11185     int sq = fromX + (fromY<<4);
11186     int piece = quickBoard[sq];
11187     quickBoard[sq] = 0;
11188     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11189     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11190         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11191         moveDatabase[movePtr++].piece = Q_WCASTL;
11192         quickBoard[sq] = piece;
11193         piece = quickBoard[from]; quickBoard[from] = 0;
11194         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11195     } else
11196     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11197         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11198         moveDatabase[movePtr++].piece = Q_BCASTL;
11199         quickBoard[sq] = piece;
11200         piece = quickBoard[from]; quickBoard[from] = 0;
11201         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11202     } else
11203     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11204         quickBoard[(fromY<<4)+toX] = 0;
11205         moveDatabase[movePtr].piece = Q_EP;
11206         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11207         moveDatabase[movePtr].to = sq;
11208     } else
11209     if(promoPiece != pieceType[piece]) {
11210         moveDatabase[movePtr++].piece = Q_PROMO;
11211         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11212     }
11213     moveDatabase[movePtr].piece = piece;
11214     quickBoard[sq] = piece;
11215     movePtr++;
11216 }
11217
11218 int
11219 PackGame (Board board)
11220 {
11221     Move *newSpace = NULL;
11222     moveDatabase[movePtr].piece = 0; // terminate previous game
11223     if(movePtr > dataSize) {
11224         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11225         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11226         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11227         if(newSpace) {
11228             int i;
11229             Move *p = moveDatabase, *q = newSpace;
11230             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11231             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11232             moveDatabase = newSpace;
11233         } else { // calloc failed, we must be out of memory. Too bad...
11234             dataSize = 0; // prevent calloc events for all subsequent games
11235             return 0;     // and signal this one isn't cached
11236         }
11237     }
11238     movePtr++;
11239     MakePieceList(board, counts);
11240     return movePtr;
11241 }
11242
11243 int
11244 QuickCompare (Board board, int *minCounts, int *maxCounts)
11245 {   // compare according to search mode
11246     int r, f;
11247     switch(appData.searchMode)
11248     {
11249       case 1: // exact position match
11250         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11251         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11252             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11253         }
11254         break;
11255       case 2: // can have extra material on empty squares
11256         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11257             if(board[r][f] == EmptySquare) continue;
11258             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11259         }
11260         break;
11261       case 3: // material with exact Pawn structure
11262         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11263             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11264             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11265         } // fall through to material comparison
11266       case 4: // exact material
11267         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11268         break;
11269       case 6: // material range with given imbalance
11270         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11271         // fall through to range comparison
11272       case 5: // material range
11273         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11274     }
11275     return TRUE;
11276 }
11277
11278 int
11279 QuickScan (Board board, Move *move)
11280 {   // reconstruct game,and compare all positions in it
11281     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11282     do {
11283         int piece = move->piece;
11284         int to = move->to, from = pieceList[piece];
11285         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11286           if(!piece) return -1;
11287           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11288             piece = (++move)->piece;
11289             from = pieceList[piece];
11290             counts[pieceType[piece]]--;
11291             pieceType[piece] = (ChessSquare) move->to;
11292             counts[move->to]++;
11293           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11294             counts[pieceType[quickBoard[to]]]--;
11295             quickBoard[to] = 0; total--;
11296             move++;
11297             continue;
11298           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11299             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11300             from  = pieceList[piece]; // so this must be King
11301             quickBoard[from] = 0;
11302             quickBoard[to] = piece;
11303             pieceList[piece] = to;
11304             move++;
11305             continue;
11306           }
11307         }
11308         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11309         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11310         quickBoard[from] = 0;
11311         quickBoard[to] = piece;
11312         pieceList[piece] = to;
11313         cnt++; turn ^= 3;
11314         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11315            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11316            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11317                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11318           ) {
11319             static int lastCounts[EmptySquare+1];
11320             int i;
11321             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11322             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11323         } else stretch = 0;
11324         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11325         move++;
11326     } while(1);
11327 }
11328
11329 void
11330 InitSearch ()
11331 {
11332     int r, f;
11333     flipSearch = FALSE;
11334     CopyBoard(soughtBoard, boards[currentMove]);
11335     soughtTotal = MakePieceList(soughtBoard, maxSought);
11336     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11337     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11338     CopyBoard(reverseBoard, boards[currentMove]);
11339     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11340         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11341         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11342         reverseBoard[r][f] = piece;
11343     }
11344     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11345     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11346     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11347                  || (boards[currentMove][CASTLING][2] == NoRights || 
11348                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11349                  && (boards[currentMove][CASTLING][5] == NoRights || 
11350                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11351       ) {
11352         flipSearch = TRUE;
11353         CopyBoard(flipBoard, soughtBoard);
11354         CopyBoard(rotateBoard, reverseBoard);
11355         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11356             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11357             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11358         }
11359     }
11360     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11361     if(appData.searchMode >= 5) {
11362         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11363         MakePieceList(soughtBoard, minSought);
11364         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11365     }
11366     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11367         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11368 }
11369
11370 GameInfo dummyInfo;
11371
11372 int
11373 GameContainsPosition (FILE *f, ListGame *lg)
11374 {
11375     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11376     int fromX, fromY, toX, toY;
11377     char promoChar;
11378     static int initDone=FALSE;
11379
11380     // weed out games based on numerical tag comparison
11381     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11382     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11383     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11384     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11385     if(!initDone) {
11386         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11387         initDone = TRUE;
11388     }
11389     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11390     else CopyBoard(boards[scratch], initialPosition); // default start position
11391     if(lg->moves) {
11392         turn = btm + 1;
11393         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11394         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11395     }
11396     if(btm) plyNr++;
11397     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11398     fseek(f, lg->offset, 0);
11399     yynewfile(f);
11400     while(1) {
11401         yyboardindex = scratch;
11402         quickFlag = plyNr+1;
11403         next = Myylex();
11404         quickFlag = 0;
11405         switch(next) {
11406             case PGNTag:
11407                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11408             default:
11409                 continue;
11410
11411             case XBoardGame:
11412             case GNUChessGame:
11413                 if(plyNr) return -1; // after we have seen moves, this is for new game
11414               continue;
11415
11416             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11417             case ImpossibleMove:
11418             case WhiteWins: // game ends here with these four
11419             case BlackWins:
11420             case GameIsDrawn:
11421             case GameUnfinished:
11422                 return -1;
11423
11424             case IllegalMove:
11425                 if(appData.testLegality) return -1;
11426             case WhiteCapturesEnPassant:
11427             case BlackCapturesEnPassant:
11428             case WhitePromotion:
11429             case BlackPromotion:
11430             case WhiteNonPromotion:
11431             case BlackNonPromotion:
11432             case NormalMove:
11433             case WhiteKingSideCastle:
11434             case WhiteQueenSideCastle:
11435             case BlackKingSideCastle:
11436             case BlackQueenSideCastle:
11437             case WhiteKingSideCastleWild:
11438             case WhiteQueenSideCastleWild:
11439             case BlackKingSideCastleWild:
11440             case BlackQueenSideCastleWild:
11441             case WhiteHSideCastleFR:
11442             case WhiteASideCastleFR:
11443             case BlackHSideCastleFR:
11444             case BlackASideCastleFR:
11445                 fromX = currentMoveString[0] - AAA;
11446                 fromY = currentMoveString[1] - ONE;
11447                 toX = currentMoveString[2] - AAA;
11448                 toY = currentMoveString[3] - ONE;
11449                 promoChar = currentMoveString[4];
11450                 break;
11451             case WhiteDrop:
11452             case BlackDrop:
11453                 fromX = next == WhiteDrop ?
11454                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11455                   (int) CharToPiece(ToLower(currentMoveString[0]));
11456                 fromY = DROP_RANK;
11457                 toX = currentMoveString[2] - AAA;
11458                 toY = currentMoveString[3] - ONE;
11459                 promoChar = 0;
11460                 break;
11461         }
11462         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11463         plyNr++;
11464         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11465         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11466         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11467         if(appData.findMirror) {
11468             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11469             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11470         }
11471     }
11472 }
11473
11474 /* Load the nth game from open file f */
11475 int
11476 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11477 {
11478     ChessMove cm;
11479     char buf[MSG_SIZ];
11480     int gn = gameNumber;
11481     ListGame *lg = NULL;
11482     int numPGNTags = 0;
11483     int err, pos = -1;
11484     GameMode oldGameMode;
11485     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11486
11487     if (appData.debugMode)
11488         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11489
11490     if (gameMode == Training )
11491         SetTrainingModeOff();
11492
11493     oldGameMode = gameMode;
11494     if (gameMode != BeginningOfGame) {
11495       Reset(FALSE, TRUE);
11496     }
11497
11498     gameFileFP = f;
11499     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11500         fclose(lastLoadGameFP);
11501     }
11502
11503     if (useList) {
11504         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11505
11506         if (lg) {
11507             fseek(f, lg->offset, 0);
11508             GameListHighlight(gameNumber);
11509             pos = lg->position;
11510             gn = 1;
11511         }
11512         else {
11513             DisplayError(_("Game number out of range"), 0);
11514             return FALSE;
11515         }
11516     } else {
11517         GameListDestroy();
11518         if (fseek(f, 0, 0) == -1) {
11519             if (f == lastLoadGameFP ?
11520                 gameNumber == lastLoadGameNumber + 1 :
11521                 gameNumber == 1) {
11522                 gn = 1;
11523             } else {
11524                 DisplayError(_("Can't seek on game file"), 0);
11525                 return FALSE;
11526             }
11527         }
11528     }
11529     lastLoadGameFP = f;
11530     lastLoadGameNumber = gameNumber;
11531     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11532     lastLoadGameUseList = useList;
11533
11534     yynewfile(f);
11535
11536     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11537       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11538                 lg->gameInfo.black);
11539             DisplayTitle(buf);
11540     } else if (*title != NULLCHAR) {
11541         if (gameNumber > 1) {
11542           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11543             DisplayTitle(buf);
11544         } else {
11545             DisplayTitle(title);
11546         }
11547     }
11548
11549     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11550         gameMode = PlayFromGameFile;
11551         ModeHighlight();
11552     }
11553
11554     currentMove = forwardMostMove = backwardMostMove = 0;
11555     CopyBoard(boards[0], initialPosition);
11556     StopClocks();
11557
11558     /*
11559      * Skip the first gn-1 games in the file.
11560      * Also skip over anything that precedes an identifiable
11561      * start of game marker, to avoid being confused by
11562      * garbage at the start of the file.  Currently
11563      * recognized start of game markers are the move number "1",
11564      * the pattern "gnuchess .* game", the pattern
11565      * "^[#;%] [^ ]* game file", and a PGN tag block.
11566      * A game that starts with one of the latter two patterns
11567      * will also have a move number 1, possibly
11568      * following a position diagram.
11569      * 5-4-02: Let's try being more lenient and allowing a game to
11570      * start with an unnumbered move.  Does that break anything?
11571      */
11572     cm = lastLoadGameStart = EndOfFile;
11573     while (gn > 0) {
11574         yyboardindex = forwardMostMove;
11575         cm = (ChessMove) Myylex();
11576         switch (cm) {
11577           case EndOfFile:
11578             if (cmailMsgLoaded) {
11579                 nCmailGames = CMAIL_MAX_GAMES - gn;
11580             } else {
11581                 Reset(TRUE, TRUE);
11582                 DisplayError(_("Game not found in file"), 0);
11583             }
11584             return FALSE;
11585
11586           case GNUChessGame:
11587           case XBoardGame:
11588             gn--;
11589             lastLoadGameStart = cm;
11590             break;
11591
11592           case MoveNumberOne:
11593             switch (lastLoadGameStart) {
11594               case GNUChessGame:
11595               case XBoardGame:
11596               case PGNTag:
11597                 break;
11598               case MoveNumberOne:
11599               case EndOfFile:
11600                 gn--;           /* count this game */
11601                 lastLoadGameStart = cm;
11602                 break;
11603               default:
11604                 /* impossible */
11605                 break;
11606             }
11607             break;
11608
11609           case PGNTag:
11610             switch (lastLoadGameStart) {
11611               case GNUChessGame:
11612               case PGNTag:
11613               case MoveNumberOne:
11614               case EndOfFile:
11615                 gn--;           /* count this game */
11616                 lastLoadGameStart = cm;
11617                 break;
11618               case XBoardGame:
11619                 lastLoadGameStart = cm; /* game counted already */
11620                 break;
11621               default:
11622                 /* impossible */
11623                 break;
11624             }
11625             if (gn > 0) {
11626                 do {
11627                     yyboardindex = forwardMostMove;
11628                     cm = (ChessMove) Myylex();
11629                 } while (cm == PGNTag || cm == Comment);
11630             }
11631             break;
11632
11633           case WhiteWins:
11634           case BlackWins:
11635           case GameIsDrawn:
11636             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11637                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11638                     != CMAIL_OLD_RESULT) {
11639                     nCmailResults ++ ;
11640                     cmailResult[  CMAIL_MAX_GAMES
11641                                 - gn - 1] = CMAIL_OLD_RESULT;
11642                 }
11643             }
11644             break;
11645
11646           case NormalMove:
11647             /* Only a NormalMove can be at the start of a game
11648              * without a position diagram. */
11649             if (lastLoadGameStart == EndOfFile ) {
11650               gn--;
11651               lastLoadGameStart = MoveNumberOne;
11652             }
11653             break;
11654
11655           default:
11656             break;
11657         }
11658     }
11659
11660     if (appData.debugMode)
11661       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11662
11663     if (cm == XBoardGame) {
11664         /* Skip any header junk before position diagram and/or move 1 */
11665         for (;;) {
11666             yyboardindex = forwardMostMove;
11667             cm = (ChessMove) Myylex();
11668
11669             if (cm == EndOfFile ||
11670                 cm == GNUChessGame || cm == XBoardGame) {
11671                 /* Empty game; pretend end-of-file and handle later */
11672                 cm = EndOfFile;
11673                 break;
11674             }
11675
11676             if (cm == MoveNumberOne || cm == PositionDiagram ||
11677                 cm == PGNTag || cm == Comment)
11678               break;
11679         }
11680     } else if (cm == GNUChessGame) {
11681         if (gameInfo.event != NULL) {
11682             free(gameInfo.event);
11683         }
11684         gameInfo.event = StrSave(yy_text);
11685     }
11686
11687     startedFromSetupPosition = FALSE;
11688     while (cm == PGNTag) {
11689         if (appData.debugMode)
11690           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11691         err = ParsePGNTag(yy_text, &gameInfo);
11692         if (!err) numPGNTags++;
11693
11694         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11695         if(gameInfo.variant != oldVariant) {
11696             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11697             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11698             InitPosition(TRUE);
11699             oldVariant = gameInfo.variant;
11700             if (appData.debugMode)
11701               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11702         }
11703
11704
11705         if (gameInfo.fen != NULL) {
11706           Board initial_position;
11707           startedFromSetupPosition = TRUE;
11708           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11709             Reset(TRUE, TRUE);
11710             DisplayError(_("Bad FEN position in file"), 0);
11711             return FALSE;
11712           }
11713           CopyBoard(boards[0], initial_position);
11714           if (blackPlaysFirst) {
11715             currentMove = forwardMostMove = backwardMostMove = 1;
11716             CopyBoard(boards[1], initial_position);
11717             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11718             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11719             timeRemaining[0][1] = whiteTimeRemaining;
11720             timeRemaining[1][1] = blackTimeRemaining;
11721             if (commentList[0] != NULL) {
11722               commentList[1] = commentList[0];
11723               commentList[0] = NULL;
11724             }
11725           } else {
11726             currentMove = forwardMostMove = backwardMostMove = 0;
11727           }
11728           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11729           {   int i;
11730               initialRulePlies = FENrulePlies;
11731               for( i=0; i< nrCastlingRights; i++ )
11732                   initialRights[i] = initial_position[CASTLING][i];
11733           }
11734           yyboardindex = forwardMostMove;
11735           free(gameInfo.fen);
11736           gameInfo.fen = NULL;
11737         }
11738
11739         yyboardindex = forwardMostMove;
11740         cm = (ChessMove) Myylex();
11741
11742         /* Handle comments interspersed among the tags */
11743         while (cm == Comment) {
11744             char *p;
11745             if (appData.debugMode)
11746               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11747             p = yy_text;
11748             AppendComment(currentMove, p, FALSE);
11749             yyboardindex = forwardMostMove;
11750             cm = (ChessMove) Myylex();
11751         }
11752     }
11753
11754     /* don't rely on existence of Event tag since if game was
11755      * pasted from clipboard the Event tag may not exist
11756      */
11757     if (numPGNTags > 0){
11758         char *tags;
11759         if (gameInfo.variant == VariantNormal) {
11760           VariantClass v = StringToVariant(gameInfo.event);
11761           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11762           if(v < VariantShogi) gameInfo.variant = v;
11763         }
11764         if (!matchMode) {
11765           if( appData.autoDisplayTags ) {
11766             tags = PGNTags(&gameInfo);
11767             TagsPopUp(tags, CmailMsg());
11768             free(tags);
11769           }
11770         }
11771     } else {
11772         /* Make something up, but don't display it now */
11773         SetGameInfo();
11774         TagsPopDown();
11775     }
11776
11777     if (cm == PositionDiagram) {
11778         int i, j;
11779         char *p;
11780         Board initial_position;
11781
11782         if (appData.debugMode)
11783           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11784
11785         if (!startedFromSetupPosition) {
11786             p = yy_text;
11787             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11788               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11789                 switch (*p) {
11790                   case '{':
11791                   case '[':
11792                   case '-':
11793                   case ' ':
11794                   case '\t':
11795                   case '\n':
11796                   case '\r':
11797                     break;
11798                   default:
11799                     initial_position[i][j++] = CharToPiece(*p);
11800                     break;
11801                 }
11802             while (*p == ' ' || *p == '\t' ||
11803                    *p == '\n' || *p == '\r') p++;
11804
11805             if (strncmp(p, "black", strlen("black"))==0)
11806               blackPlaysFirst = TRUE;
11807             else
11808               blackPlaysFirst = FALSE;
11809             startedFromSetupPosition = TRUE;
11810
11811             CopyBoard(boards[0], initial_position);
11812             if (blackPlaysFirst) {
11813                 currentMove = forwardMostMove = backwardMostMove = 1;
11814                 CopyBoard(boards[1], initial_position);
11815                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11816                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11817                 timeRemaining[0][1] = whiteTimeRemaining;
11818                 timeRemaining[1][1] = blackTimeRemaining;
11819                 if (commentList[0] != NULL) {
11820                     commentList[1] = commentList[0];
11821                     commentList[0] = NULL;
11822                 }
11823             } else {
11824                 currentMove = forwardMostMove = backwardMostMove = 0;
11825             }
11826         }
11827         yyboardindex = forwardMostMove;
11828         cm = (ChessMove) Myylex();
11829     }
11830
11831     if (first.pr == NoProc) {
11832         StartChessProgram(&first);
11833     }
11834     InitChessProgram(&first, FALSE);
11835     SendToProgram("force\n", &first);
11836     if (startedFromSetupPosition) {
11837         SendBoard(&first, forwardMostMove);
11838     if (appData.debugMode) {
11839         fprintf(debugFP, "Load Game\n");
11840     }
11841         DisplayBothClocks();
11842     }
11843
11844     /* [HGM] server: flag to write setup moves in broadcast file as one */
11845     loadFlag = appData.suppressLoadMoves;
11846
11847     while (cm == Comment) {
11848         char *p;
11849         if (appData.debugMode)
11850           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11851         p = yy_text;
11852         AppendComment(currentMove, p, FALSE);
11853         yyboardindex = forwardMostMove;
11854         cm = (ChessMove) Myylex();
11855     }
11856
11857     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11858         cm == WhiteWins || cm == BlackWins ||
11859         cm == GameIsDrawn || cm == GameUnfinished) {
11860         DisplayMessage("", _("No moves in game"));
11861         if (cmailMsgLoaded) {
11862             if (appData.debugMode)
11863               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11864             ClearHighlights();
11865             flipView = FALSE;
11866         }
11867         DrawPosition(FALSE, boards[currentMove]);
11868         DisplayBothClocks();
11869         gameMode = EditGame;
11870         ModeHighlight();
11871         gameFileFP = NULL;
11872         cmailOldMove = 0;
11873         return TRUE;
11874     }
11875
11876     // [HGM] PV info: routine tests if comment empty
11877     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11878         DisplayComment(currentMove - 1, commentList[currentMove]);
11879     }
11880     if (!matchMode && appData.timeDelay != 0)
11881       DrawPosition(FALSE, boards[currentMove]);
11882
11883     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11884       programStats.ok_to_send = 1;
11885     }
11886
11887     /* if the first token after the PGN tags is a move
11888      * and not move number 1, retrieve it from the parser
11889      */
11890     if (cm != MoveNumberOne)
11891         LoadGameOneMove(cm);
11892
11893     /* load the remaining moves from the file */
11894     while (LoadGameOneMove(EndOfFile)) {
11895       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11896       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11897     }
11898
11899     /* rewind to the start of the game */
11900     currentMove = backwardMostMove;
11901
11902     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11903
11904     if (oldGameMode == AnalyzeFile ||
11905         oldGameMode == AnalyzeMode) {
11906       AnalyzeFileEvent();
11907     }
11908
11909     if (!matchMode && pos >= 0) {
11910         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11911     } else
11912     if (matchMode || appData.timeDelay == 0) {
11913       ToEndEvent();
11914     } else if (appData.timeDelay > 0) {
11915       AutoPlayGameLoop();
11916     }
11917
11918     if (appData.debugMode)
11919         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11920
11921     loadFlag = 0; /* [HGM] true game starts */
11922     return TRUE;
11923 }
11924
11925 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11926 int
11927 ReloadPosition (int offset)
11928 {
11929     int positionNumber = lastLoadPositionNumber + offset;
11930     if (lastLoadPositionFP == NULL) {
11931         DisplayError(_("No position has been loaded yet"), 0);
11932         return FALSE;
11933     }
11934     if (positionNumber <= 0) {
11935         DisplayError(_("Can't back up any further"), 0);
11936         return FALSE;
11937     }
11938     return LoadPosition(lastLoadPositionFP, positionNumber,
11939                         lastLoadPositionTitle);
11940 }
11941
11942 /* Load the nth position from the given file */
11943 int
11944 LoadPositionFromFile (char *filename, int n, char *title)
11945 {
11946     FILE *f;
11947     char buf[MSG_SIZ];
11948
11949     if (strcmp(filename, "-") == 0) {
11950         return LoadPosition(stdin, n, "stdin");
11951     } else {
11952         f = fopen(filename, "rb");
11953         if (f == NULL) {
11954             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11955             DisplayError(buf, errno);
11956             return FALSE;
11957         } else {
11958             return LoadPosition(f, n, title);
11959         }
11960     }
11961 }
11962
11963 /* Load the nth position from the given open file, and close it */
11964 int
11965 LoadPosition (FILE *f, int positionNumber, char *title)
11966 {
11967     char *p, line[MSG_SIZ];
11968     Board initial_position;
11969     int i, j, fenMode, pn;
11970
11971     if (gameMode == Training )
11972         SetTrainingModeOff();
11973
11974     if (gameMode != BeginningOfGame) {
11975         Reset(FALSE, TRUE);
11976     }
11977     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11978         fclose(lastLoadPositionFP);
11979     }
11980     if (positionNumber == 0) positionNumber = 1;
11981     lastLoadPositionFP = f;
11982     lastLoadPositionNumber = positionNumber;
11983     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11984     if (first.pr == NoProc && !appData.noChessProgram) {
11985       StartChessProgram(&first);
11986       InitChessProgram(&first, FALSE);
11987     }
11988     pn = positionNumber;
11989     if (positionNumber < 0) {
11990         /* Negative position number means to seek to that byte offset */
11991         if (fseek(f, -positionNumber, 0) == -1) {
11992             DisplayError(_("Can't seek on position file"), 0);
11993             return FALSE;
11994         };
11995         pn = 1;
11996     } else {
11997         if (fseek(f, 0, 0) == -1) {
11998             if (f == lastLoadPositionFP ?
11999                 positionNumber == lastLoadPositionNumber + 1 :
12000                 positionNumber == 1) {
12001                 pn = 1;
12002             } else {
12003                 DisplayError(_("Can't seek on position file"), 0);
12004                 return FALSE;
12005             }
12006         }
12007     }
12008     /* See if this file is FEN or old-style xboard */
12009     if (fgets(line, MSG_SIZ, f) == NULL) {
12010         DisplayError(_("Position not found in file"), 0);
12011         return FALSE;
12012     }
12013     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12014     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12015
12016     if (pn >= 2) {
12017         if (fenMode || line[0] == '#') pn--;
12018         while (pn > 0) {
12019             /* skip positions before number pn */
12020             if (fgets(line, MSG_SIZ, f) == NULL) {
12021                 Reset(TRUE, TRUE);
12022                 DisplayError(_("Position not found in file"), 0);
12023                 return FALSE;
12024             }
12025             if (fenMode || line[0] == '#') pn--;
12026         }
12027     }
12028
12029     if (fenMode) {
12030         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12031             DisplayError(_("Bad FEN position in file"), 0);
12032             return FALSE;
12033         }
12034     } else {
12035         (void) fgets(line, MSG_SIZ, f);
12036         (void) fgets(line, MSG_SIZ, f);
12037
12038         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12039             (void) fgets(line, MSG_SIZ, f);
12040             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12041                 if (*p == ' ')
12042                   continue;
12043                 initial_position[i][j++] = CharToPiece(*p);
12044             }
12045         }
12046
12047         blackPlaysFirst = FALSE;
12048         if (!feof(f)) {
12049             (void) fgets(line, MSG_SIZ, f);
12050             if (strncmp(line, "black", strlen("black"))==0)
12051               blackPlaysFirst = TRUE;
12052         }
12053     }
12054     startedFromSetupPosition = TRUE;
12055
12056     CopyBoard(boards[0], initial_position);
12057     if (blackPlaysFirst) {
12058         currentMove = forwardMostMove = backwardMostMove = 1;
12059         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12060         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12061         CopyBoard(boards[1], initial_position);
12062         DisplayMessage("", _("Black to play"));
12063     } else {
12064         currentMove = forwardMostMove = backwardMostMove = 0;
12065         DisplayMessage("", _("White to play"));
12066     }
12067     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12068     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12069         SendToProgram("force\n", &first);
12070         SendBoard(&first, forwardMostMove);
12071     }
12072     if (appData.debugMode) {
12073 int i, j;
12074   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12075   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12076         fprintf(debugFP, "Load Position\n");
12077     }
12078
12079     if (positionNumber > 1) {
12080       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12081         DisplayTitle(line);
12082     } else {
12083         DisplayTitle(title);
12084     }
12085     gameMode = EditGame;
12086     ModeHighlight();
12087     ResetClocks();
12088     timeRemaining[0][1] = whiteTimeRemaining;
12089     timeRemaining[1][1] = blackTimeRemaining;
12090     DrawPosition(FALSE, boards[currentMove]);
12091
12092     return TRUE;
12093 }
12094
12095
12096 void
12097 CopyPlayerNameIntoFileName (char **dest, char *src)
12098 {
12099     while (*src != NULLCHAR && *src != ',') {
12100         if (*src == ' ') {
12101             *(*dest)++ = '_';
12102             src++;
12103         } else {
12104             *(*dest)++ = *src++;
12105         }
12106     }
12107 }
12108
12109 char *
12110 DefaultFileName (char *ext)
12111 {
12112     static char def[MSG_SIZ];
12113     char *p;
12114
12115     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12116         p = def;
12117         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12118         *p++ = '-';
12119         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12120         *p++ = '.';
12121         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12122     } else {
12123         def[0] = NULLCHAR;
12124     }
12125     return def;
12126 }
12127
12128 /* Save the current game to the given file */
12129 int
12130 SaveGameToFile (char *filename, int append)
12131 {
12132     FILE *f;
12133     char buf[MSG_SIZ];
12134     int result, i, t,tot=0;
12135
12136     if (strcmp(filename, "-") == 0) {
12137         return SaveGame(stdout, 0, NULL);
12138     } else {
12139         for(i=0; i<10; i++) { // upto 10 tries
12140              f = fopen(filename, append ? "a" : "w");
12141              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12142              if(f || errno != 13) break;
12143              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12144              tot += t;
12145         }
12146         if (f == NULL) {
12147             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12148             DisplayError(buf, errno);
12149             return FALSE;
12150         } else {
12151             safeStrCpy(buf, lastMsg, MSG_SIZ);
12152             DisplayMessage(_("Waiting for access to save file"), "");
12153             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12154             DisplayMessage(_("Saving game"), "");
12155             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12156             result = SaveGame(f, 0, NULL);
12157             DisplayMessage(buf, "");
12158             return result;
12159         }
12160     }
12161 }
12162
12163 char *
12164 SavePart (char *str)
12165 {
12166     static char buf[MSG_SIZ];
12167     char *p;
12168
12169     p = strchr(str, ' ');
12170     if (p == NULL) return str;
12171     strncpy(buf, str, p - str);
12172     buf[p - str] = NULLCHAR;
12173     return buf;
12174 }
12175
12176 #define PGN_MAX_LINE 75
12177
12178 #define PGN_SIDE_WHITE  0
12179 #define PGN_SIDE_BLACK  1
12180
12181 static int
12182 FindFirstMoveOutOfBook (int side)
12183 {
12184     int result = -1;
12185
12186     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12187         int index = backwardMostMove;
12188         int has_book_hit = 0;
12189
12190         if( (index % 2) != side ) {
12191             index++;
12192         }
12193
12194         while( index < forwardMostMove ) {
12195             /* Check to see if engine is in book */
12196             int depth = pvInfoList[index].depth;
12197             int score = pvInfoList[index].score;
12198             int in_book = 0;
12199
12200             if( depth <= 2 ) {
12201                 in_book = 1;
12202             }
12203             else if( score == 0 && depth == 63 ) {
12204                 in_book = 1; /* Zappa */
12205             }
12206             else if( score == 2 && depth == 99 ) {
12207                 in_book = 1; /* Abrok */
12208             }
12209
12210             has_book_hit += in_book;
12211
12212             if( ! in_book ) {
12213                 result = index;
12214
12215                 break;
12216             }
12217
12218             index += 2;
12219         }
12220     }
12221
12222     return result;
12223 }
12224
12225 void
12226 GetOutOfBookInfo (char * buf)
12227 {
12228     int oob[2];
12229     int i;
12230     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12231
12232     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12233     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12234
12235     *buf = '\0';
12236
12237     if( oob[0] >= 0 || oob[1] >= 0 ) {
12238         for( i=0; i<2; i++ ) {
12239             int idx = oob[i];
12240
12241             if( idx >= 0 ) {
12242                 if( i > 0 && oob[0] >= 0 ) {
12243                     strcat( buf, "   " );
12244                 }
12245
12246                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12247                 sprintf( buf+strlen(buf), "%s%.2f",
12248                     pvInfoList[idx].score >= 0 ? "+" : "",
12249                     pvInfoList[idx].score / 100.0 );
12250             }
12251         }
12252     }
12253 }
12254
12255 /* Save game in PGN style and close the file */
12256 int
12257 SaveGamePGN (FILE *f)
12258 {
12259     int i, offset, linelen, newblock;
12260     time_t tm;
12261 //    char *movetext;
12262     char numtext[32];
12263     int movelen, numlen, blank;
12264     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12265
12266     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12267
12268     tm = time((time_t *) NULL);
12269
12270     PrintPGNTags(f, &gameInfo);
12271
12272     if (backwardMostMove > 0 || startedFromSetupPosition) {
12273         char *fen = PositionToFEN(backwardMostMove, NULL);
12274         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12275         fprintf(f, "\n{--------------\n");
12276         PrintPosition(f, backwardMostMove);
12277         fprintf(f, "--------------}\n");
12278         free(fen);
12279     }
12280     else {
12281         /* [AS] Out of book annotation */
12282         if( appData.saveOutOfBookInfo ) {
12283             char buf[64];
12284
12285             GetOutOfBookInfo( buf );
12286
12287             if( buf[0] != '\0' ) {
12288                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12289             }
12290         }
12291
12292         fprintf(f, "\n");
12293     }
12294
12295     i = backwardMostMove;
12296     linelen = 0;
12297     newblock = TRUE;
12298
12299     while (i < forwardMostMove) {
12300         /* Print comments preceding this move */
12301         if (commentList[i] != NULL) {
12302             if (linelen > 0) fprintf(f, "\n");
12303             fprintf(f, "%s", commentList[i]);
12304             linelen = 0;
12305             newblock = TRUE;
12306         }
12307
12308         /* Format move number */
12309         if ((i % 2) == 0)
12310           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12311         else
12312           if (newblock)
12313             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12314           else
12315             numtext[0] = NULLCHAR;
12316
12317         numlen = strlen(numtext);
12318         newblock = FALSE;
12319
12320         /* Print move number */
12321         blank = linelen > 0 && numlen > 0;
12322         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12323             fprintf(f, "\n");
12324             linelen = 0;
12325             blank = 0;
12326         }
12327         if (blank) {
12328             fprintf(f, " ");
12329             linelen++;
12330         }
12331         fprintf(f, "%s", numtext);
12332         linelen += numlen;
12333
12334         /* Get move */
12335         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12336         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12337
12338         /* Print move */
12339         blank = linelen > 0 && movelen > 0;
12340         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12341             fprintf(f, "\n");
12342             linelen = 0;
12343             blank = 0;
12344         }
12345         if (blank) {
12346             fprintf(f, " ");
12347             linelen++;
12348         }
12349         fprintf(f, "%s", move_buffer);
12350         linelen += movelen;
12351
12352         /* [AS] Add PV info if present */
12353         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12354             /* [HGM] add time */
12355             char buf[MSG_SIZ]; int seconds;
12356
12357             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12358
12359             if( seconds <= 0)
12360               buf[0] = 0;
12361             else
12362               if( seconds < 30 )
12363                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12364               else
12365                 {
12366                   seconds = (seconds + 4)/10; // round to full seconds
12367                   if( seconds < 60 )
12368                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12369                   else
12370                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12371                 }
12372
12373             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12374                       pvInfoList[i].score >= 0 ? "+" : "",
12375                       pvInfoList[i].score / 100.0,
12376                       pvInfoList[i].depth,
12377                       buf );
12378
12379             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12380
12381             /* Print score/depth */
12382             blank = linelen > 0 && movelen > 0;
12383             if (linelen + (blank ? 1 : 0) + movelen > 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", move_buffer);
12393             linelen += movelen;
12394         }
12395
12396         i++;
12397     }
12398
12399     /* Start a new line */
12400     if (linelen > 0) fprintf(f, "\n");
12401
12402     /* Print comments after last move */
12403     if (commentList[i] != NULL) {
12404         fprintf(f, "%s\n", commentList[i]);
12405     }
12406
12407     /* Print result */
12408     if (gameInfo.resultDetails != NULL &&
12409         gameInfo.resultDetails[0] != NULLCHAR) {
12410         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12411                 PGNResult(gameInfo.result));
12412     } else {
12413         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12414     }
12415
12416     fclose(f);
12417     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12418     return TRUE;
12419 }
12420
12421 /* Save game in old style and close the file */
12422 int
12423 SaveGameOldStyle (FILE *f)
12424 {
12425     int i, offset;
12426     time_t tm;
12427
12428     tm = time((time_t *) NULL);
12429
12430     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12431     PrintOpponents(f);
12432
12433     if (backwardMostMove > 0 || startedFromSetupPosition) {
12434         fprintf(f, "\n[--------------\n");
12435         PrintPosition(f, backwardMostMove);
12436         fprintf(f, "--------------]\n");
12437     } else {
12438         fprintf(f, "\n");
12439     }
12440
12441     i = backwardMostMove;
12442     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12443
12444     while (i < forwardMostMove) {
12445         if (commentList[i] != NULL) {
12446             fprintf(f, "[%s]\n", commentList[i]);
12447         }
12448
12449         if ((i % 2) == 1) {
12450             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12451             i++;
12452         } else {
12453             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12454             i++;
12455             if (commentList[i] != NULL) {
12456                 fprintf(f, "\n");
12457                 continue;
12458             }
12459             if (i >= forwardMostMove) {
12460                 fprintf(f, "\n");
12461                 break;
12462             }
12463             fprintf(f, "%s\n", parseList[i]);
12464             i++;
12465         }
12466     }
12467
12468     if (commentList[i] != NULL) {
12469         fprintf(f, "[%s]\n", commentList[i]);
12470     }
12471
12472     /* This isn't really the old style, but it's close enough */
12473     if (gameInfo.resultDetails != NULL &&
12474         gameInfo.resultDetails[0] != NULLCHAR) {
12475         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12476                 gameInfo.resultDetails);
12477     } else {
12478         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12479     }
12480
12481     fclose(f);
12482     return TRUE;
12483 }
12484
12485 /* Save the current game to open file f and close the file */
12486 int
12487 SaveGame (FILE *f, int dummy, char *dummy2)
12488 {
12489     if (gameMode == EditPosition) EditPositionDone(TRUE);
12490     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12491     if (appData.oldSaveStyle)
12492       return SaveGameOldStyle(f);
12493     else
12494       return SaveGamePGN(f);
12495 }
12496
12497 /* Save the current position to the given file */
12498 int
12499 SavePositionToFile (char *filename)
12500 {
12501     FILE *f;
12502     char buf[MSG_SIZ];
12503
12504     if (strcmp(filename, "-") == 0) {
12505         return SavePosition(stdout, 0, NULL);
12506     } else {
12507         f = fopen(filename, "a");
12508         if (f == NULL) {
12509             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12510             DisplayError(buf, errno);
12511             return FALSE;
12512         } else {
12513             safeStrCpy(buf, lastMsg, MSG_SIZ);
12514             DisplayMessage(_("Waiting for access to save file"), "");
12515             flock(fileno(f), LOCK_EX); // [HGM] lock
12516             DisplayMessage(_("Saving position"), "");
12517             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12518             SavePosition(f, 0, NULL);
12519             DisplayMessage(buf, "");
12520             return TRUE;
12521         }
12522     }
12523 }
12524
12525 /* Save the current position to the given open file and close the file */
12526 int
12527 SavePosition (FILE *f, int dummy, char *dummy2)
12528 {
12529     time_t tm;
12530     char *fen;
12531
12532     if (gameMode == EditPosition) EditPositionDone(TRUE);
12533     if (appData.oldSaveStyle) {
12534         tm = time((time_t *) NULL);
12535
12536         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12537         PrintOpponents(f);
12538         fprintf(f, "[--------------\n");
12539         PrintPosition(f, currentMove);
12540         fprintf(f, "--------------]\n");
12541     } else {
12542         fen = PositionToFEN(currentMove, NULL);
12543         fprintf(f, "%s\n", fen);
12544         free(fen);
12545     }
12546     fclose(f);
12547     return TRUE;
12548 }
12549
12550 void
12551 ReloadCmailMsgEvent (int unregister)
12552 {
12553 #if !WIN32
12554     static char *inFilename = NULL;
12555     static char *outFilename;
12556     int i;
12557     struct stat inbuf, outbuf;
12558     int status;
12559
12560     /* Any registered moves are unregistered if unregister is set, */
12561     /* i.e. invoked by the signal handler */
12562     if (unregister) {
12563         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12564             cmailMoveRegistered[i] = FALSE;
12565             if (cmailCommentList[i] != NULL) {
12566                 free(cmailCommentList[i]);
12567                 cmailCommentList[i] = NULL;
12568             }
12569         }
12570         nCmailMovesRegistered = 0;
12571     }
12572
12573     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12574         cmailResult[i] = CMAIL_NOT_RESULT;
12575     }
12576     nCmailResults = 0;
12577
12578     if (inFilename == NULL) {
12579         /* Because the filenames are static they only get malloced once  */
12580         /* and they never get freed                                      */
12581         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12582         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12583
12584         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12585         sprintf(outFilename, "%s.out", appData.cmailGameName);
12586     }
12587
12588     status = stat(outFilename, &outbuf);
12589     if (status < 0) {
12590         cmailMailedMove = FALSE;
12591     } else {
12592         status = stat(inFilename, &inbuf);
12593         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12594     }
12595
12596     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12597        counts the games, notes how each one terminated, etc.
12598
12599        It would be nice to remove this kludge and instead gather all
12600        the information while building the game list.  (And to keep it
12601        in the game list nodes instead of having a bunch of fixed-size
12602        parallel arrays.)  Note this will require getting each game's
12603        termination from the PGN tags, as the game list builder does
12604        not process the game moves.  --mann
12605        */
12606     cmailMsgLoaded = TRUE;
12607     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12608
12609     /* Load first game in the file or popup game menu */
12610     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12611
12612 #endif /* !WIN32 */
12613     return;
12614 }
12615
12616 int
12617 RegisterMove ()
12618 {
12619     FILE *f;
12620     char string[MSG_SIZ];
12621
12622     if (   cmailMailedMove
12623         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12624         return TRUE;            /* Allow free viewing  */
12625     }
12626
12627     /* Unregister move to ensure that we don't leave RegisterMove        */
12628     /* with the move registered when the conditions for registering no   */
12629     /* longer hold                                                       */
12630     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12631         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12632         nCmailMovesRegistered --;
12633
12634         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12635           {
12636               free(cmailCommentList[lastLoadGameNumber - 1]);
12637               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12638           }
12639     }
12640
12641     if (cmailOldMove == -1) {
12642         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12643         return FALSE;
12644     }
12645
12646     if (currentMove > cmailOldMove + 1) {
12647         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12648         return FALSE;
12649     }
12650
12651     if (currentMove < cmailOldMove) {
12652         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12653         return FALSE;
12654     }
12655
12656     if (forwardMostMove > currentMove) {
12657         /* Silently truncate extra moves */
12658         TruncateGame();
12659     }
12660
12661     if (   (currentMove == cmailOldMove + 1)
12662         || (   (currentMove == cmailOldMove)
12663             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12664                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12665         if (gameInfo.result != GameUnfinished) {
12666             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12667         }
12668
12669         if (commentList[currentMove] != NULL) {
12670             cmailCommentList[lastLoadGameNumber - 1]
12671               = StrSave(commentList[currentMove]);
12672         }
12673         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12674
12675         if (appData.debugMode)
12676           fprintf(debugFP, "Saving %s for game %d\n",
12677                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12678
12679         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12680
12681         f = fopen(string, "w");
12682         if (appData.oldSaveStyle) {
12683             SaveGameOldStyle(f); /* also closes the file */
12684
12685             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12686             f = fopen(string, "w");
12687             SavePosition(f, 0, NULL); /* also closes the file */
12688         } else {
12689             fprintf(f, "{--------------\n");
12690             PrintPosition(f, currentMove);
12691             fprintf(f, "--------------}\n\n");
12692
12693             SaveGame(f, 0, NULL); /* also closes the file*/
12694         }
12695
12696         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12697         nCmailMovesRegistered ++;
12698     } else if (nCmailGames == 1) {
12699         DisplayError(_("You have not made a move yet"), 0);
12700         return FALSE;
12701     }
12702
12703     return TRUE;
12704 }
12705
12706 void
12707 MailMoveEvent ()
12708 {
12709 #if !WIN32
12710     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12711     FILE *commandOutput;
12712     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12713     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12714     int nBuffers;
12715     int i;
12716     int archived;
12717     char *arcDir;
12718
12719     if (! cmailMsgLoaded) {
12720         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12721         return;
12722     }
12723
12724     if (nCmailGames == nCmailResults) {
12725         DisplayError(_("No unfinished games"), 0);
12726         return;
12727     }
12728
12729 #if CMAIL_PROHIBIT_REMAIL
12730     if (cmailMailedMove) {
12731       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);
12732         DisplayError(msg, 0);
12733         return;
12734     }
12735 #endif
12736
12737     if (! (cmailMailedMove || RegisterMove())) return;
12738
12739     if (   cmailMailedMove
12740         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12741       snprintf(string, MSG_SIZ, partCommandString,
12742                appData.debugMode ? " -v" : "", appData.cmailGameName);
12743         commandOutput = popen(string, "r");
12744
12745         if (commandOutput == NULL) {
12746             DisplayError(_("Failed to invoke cmail"), 0);
12747         } else {
12748             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12749                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12750             }
12751             if (nBuffers > 1) {
12752                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12753                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12754                 nBytes = MSG_SIZ - 1;
12755             } else {
12756                 (void) memcpy(msg, buffer, nBytes);
12757             }
12758             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12759
12760             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12761                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12762
12763                 archived = TRUE;
12764                 for (i = 0; i < nCmailGames; i ++) {
12765                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12766                         archived = FALSE;
12767                     }
12768                 }
12769                 if (   archived
12770                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12771                         != NULL)) {
12772                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12773                            arcDir,
12774                            appData.cmailGameName,
12775                            gameInfo.date);
12776                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12777                     cmailMsgLoaded = FALSE;
12778                 }
12779             }
12780
12781             DisplayInformation(msg);
12782             pclose(commandOutput);
12783         }
12784     } else {
12785         if ((*cmailMsg) != '\0') {
12786             DisplayInformation(cmailMsg);
12787         }
12788     }
12789
12790     return;
12791 #endif /* !WIN32 */
12792 }
12793
12794 char *
12795 CmailMsg ()
12796 {
12797 #if WIN32
12798     return NULL;
12799 #else
12800     int  prependComma = 0;
12801     char number[5];
12802     char string[MSG_SIZ];       /* Space for game-list */
12803     int  i;
12804
12805     if (!cmailMsgLoaded) return "";
12806
12807     if (cmailMailedMove) {
12808       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12809     } else {
12810         /* Create a list of games left */
12811       snprintf(string, MSG_SIZ, "[");
12812         for (i = 0; i < nCmailGames; i ++) {
12813             if (! (   cmailMoveRegistered[i]
12814                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12815                 if (prependComma) {
12816                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12817                 } else {
12818                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12819                     prependComma = 1;
12820                 }
12821
12822                 strcat(string, number);
12823             }
12824         }
12825         strcat(string, "]");
12826
12827         if (nCmailMovesRegistered + nCmailResults == 0) {
12828             switch (nCmailGames) {
12829               case 1:
12830                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12831                 break;
12832
12833               case 2:
12834                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12835                 break;
12836
12837               default:
12838                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12839                          nCmailGames);
12840                 break;
12841             }
12842         } else {
12843             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12844               case 1:
12845                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12846                          string);
12847                 break;
12848
12849               case 0:
12850                 if (nCmailResults == nCmailGames) {
12851                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12852                 } else {
12853                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12854                 }
12855                 break;
12856
12857               default:
12858                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12859                          string);
12860             }
12861         }
12862     }
12863     return cmailMsg;
12864 #endif /* WIN32 */
12865 }
12866
12867 void
12868 ResetGameEvent ()
12869 {
12870     if (gameMode == Training)
12871       SetTrainingModeOff();
12872
12873     Reset(TRUE, TRUE);
12874     cmailMsgLoaded = FALSE;
12875     if (appData.icsActive) {
12876       SendToICS(ics_prefix);
12877       SendToICS("refresh\n");
12878     }
12879 }
12880
12881 void
12882 ExitEvent (int status)
12883 {
12884     exiting++;
12885     if (exiting > 2) {
12886       /* Give up on clean exit */
12887       exit(status);
12888     }
12889     if (exiting > 1) {
12890       /* Keep trying for clean exit */
12891       return;
12892     }
12893
12894     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12895
12896     if (telnetISR != NULL) {
12897       RemoveInputSource(telnetISR);
12898     }
12899     if (icsPR != NoProc) {
12900       DestroyChildProcess(icsPR, TRUE);
12901     }
12902
12903     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12904     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12905
12906     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12907     /* make sure this other one finishes before killing it!                  */
12908     if(endingGame) { int count = 0;
12909         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12910         while(endingGame && count++ < 10) DoSleep(1);
12911         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12912     }
12913
12914     /* Kill off chess programs */
12915     if (first.pr != NoProc) {
12916         ExitAnalyzeMode();
12917
12918         DoSleep( appData.delayBeforeQuit );
12919         SendToProgram("quit\n", &first);
12920         DoSleep( appData.delayAfterQuit );
12921         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12922     }
12923     if (second.pr != NoProc) {
12924         DoSleep( appData.delayBeforeQuit );
12925         SendToProgram("quit\n", &second);
12926         DoSleep( appData.delayAfterQuit );
12927         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12928     }
12929     if (first.isr != NULL) {
12930         RemoveInputSource(first.isr);
12931     }
12932     if (second.isr != NULL) {
12933         RemoveInputSource(second.isr);
12934     }
12935
12936     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12937     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12938
12939     ShutDownFrontEnd();
12940     exit(status);
12941 }
12942
12943 void
12944 PauseEvent ()
12945 {
12946     if (appData.debugMode)
12947         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12948     if (pausing) {
12949         pausing = FALSE;
12950         ModeHighlight();
12951         if (gameMode == MachinePlaysWhite ||
12952             gameMode == MachinePlaysBlack) {
12953             StartClocks();
12954         } else {
12955             DisplayBothClocks();
12956         }
12957         if (gameMode == PlayFromGameFile) {
12958             if (appData.timeDelay >= 0)
12959                 AutoPlayGameLoop();
12960         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12961             Reset(FALSE, TRUE);
12962             SendToICS(ics_prefix);
12963             SendToICS("refresh\n");
12964         } else if (currentMove < forwardMostMove) {
12965             ForwardInner(forwardMostMove);
12966         }
12967         pauseExamInvalid = FALSE;
12968     } else {
12969         switch (gameMode) {
12970           default:
12971             return;
12972           case IcsExamining:
12973             pauseExamForwardMostMove = forwardMostMove;
12974             pauseExamInvalid = FALSE;
12975             /* fall through */
12976           case IcsObserving:
12977           case IcsPlayingWhite:
12978           case IcsPlayingBlack:
12979             pausing = TRUE;
12980             ModeHighlight();
12981             return;
12982           case PlayFromGameFile:
12983             (void) StopLoadGameTimer();
12984             pausing = TRUE;
12985             ModeHighlight();
12986             break;
12987           case BeginningOfGame:
12988             if (appData.icsActive) return;
12989             /* else fall through */
12990           case MachinePlaysWhite:
12991           case MachinePlaysBlack:
12992           case TwoMachinesPlay:
12993             if (forwardMostMove == 0)
12994               return;           /* don't pause if no one has moved */
12995             if ((gameMode == MachinePlaysWhite &&
12996                  !WhiteOnMove(forwardMostMove)) ||
12997                 (gameMode == MachinePlaysBlack &&
12998                  WhiteOnMove(forwardMostMove))) {
12999                 StopClocks();
13000             }
13001             pausing = TRUE;
13002             ModeHighlight();
13003             break;
13004         }
13005     }
13006 }
13007
13008 void
13009 EditCommentEvent ()
13010 {
13011     char title[MSG_SIZ];
13012
13013     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13014       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13015     } else {
13016       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13017                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13018                parseList[currentMove - 1]);
13019     }
13020
13021     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13022 }
13023
13024
13025 void
13026 EditTagsEvent ()
13027 {
13028     char *tags = PGNTags(&gameInfo);
13029     bookUp = FALSE;
13030     EditTagsPopUp(tags, NULL);
13031     free(tags);
13032 }
13033
13034 void
13035 AnalyzeModeEvent ()
13036 {
13037     if (appData.noChessProgram || gameMode == AnalyzeMode)
13038       return;
13039
13040     if (gameMode != AnalyzeFile) {
13041         if (!appData.icsEngineAnalyze) {
13042                EditGameEvent();
13043                if (gameMode != EditGame) return;
13044         }
13045         ResurrectChessProgram();
13046         SendToProgram("analyze\n", &first);
13047         first.analyzing = TRUE;
13048         /*first.maybeThinking = TRUE;*/
13049         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13050         EngineOutputPopUp();
13051     }
13052     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13053     pausing = FALSE;
13054     ModeHighlight();
13055     SetGameInfo();
13056
13057     StartAnalysisClock();
13058     GetTimeMark(&lastNodeCountTime);
13059     lastNodeCount = 0;
13060 }
13061
13062 void
13063 AnalyzeFileEvent ()
13064 {
13065     if (appData.noChessProgram || gameMode == AnalyzeFile)
13066       return;
13067
13068     if (gameMode != AnalyzeMode) {
13069         EditGameEvent();
13070         if (gameMode != EditGame) return;
13071         ResurrectChessProgram();
13072         SendToProgram("analyze\n", &first);
13073         first.analyzing = TRUE;
13074         /*first.maybeThinking = TRUE;*/
13075         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13076         EngineOutputPopUp();
13077     }
13078     gameMode = AnalyzeFile;
13079     pausing = FALSE;
13080     ModeHighlight();
13081     SetGameInfo();
13082
13083     StartAnalysisClock();
13084     GetTimeMark(&lastNodeCountTime);
13085     lastNodeCount = 0;
13086     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13087 }
13088
13089 void
13090 MachineWhiteEvent ()
13091 {
13092     char buf[MSG_SIZ];
13093     char *bookHit = NULL;
13094
13095     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13096       return;
13097
13098
13099     if (gameMode == PlayFromGameFile ||
13100         gameMode == TwoMachinesPlay  ||
13101         gameMode == Training         ||
13102         gameMode == AnalyzeMode      ||
13103         gameMode == EndOfGame)
13104         EditGameEvent();
13105
13106     if (gameMode == EditPosition)
13107         EditPositionDone(TRUE);
13108
13109     if (!WhiteOnMove(currentMove)) {
13110         DisplayError(_("It is not White's turn"), 0);
13111         return;
13112     }
13113
13114     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13115       ExitAnalyzeMode();
13116
13117     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13118         gameMode == AnalyzeFile)
13119         TruncateGame();
13120
13121     ResurrectChessProgram();    /* in case it isn't running */
13122     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13123         gameMode = MachinePlaysWhite;
13124         ResetClocks();
13125     } else
13126     gameMode = MachinePlaysWhite;
13127     pausing = FALSE;
13128     ModeHighlight();
13129     SetGameInfo();
13130     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13131     DisplayTitle(buf);
13132     if (first.sendName) {
13133       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13134       SendToProgram(buf, &first);
13135     }
13136     if (first.sendTime) {
13137       if (first.useColors) {
13138         SendToProgram("black\n", &first); /*gnu kludge*/
13139       }
13140       SendTimeRemaining(&first, TRUE);
13141     }
13142     if (first.useColors) {
13143       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13144     }
13145     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13146     SetMachineThinkingEnables();
13147     first.maybeThinking = TRUE;
13148     StartClocks();
13149     firstMove = FALSE;
13150
13151     if (appData.autoFlipView && !flipView) {
13152       flipView = !flipView;
13153       DrawPosition(FALSE, NULL);
13154       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13155     }
13156
13157     if(bookHit) { // [HGM] book: simulate book reply
13158         static char bookMove[MSG_SIZ]; // a bit generous?
13159
13160         programStats.nodes = programStats.depth = programStats.time =
13161         programStats.score = programStats.got_only_move = 0;
13162         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13163
13164         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13165         strcat(bookMove, bookHit);
13166         HandleMachineMove(bookMove, &first);
13167     }
13168 }
13169
13170 void
13171 MachineBlackEvent ()
13172 {
13173   char buf[MSG_SIZ];
13174   char *bookHit = NULL;
13175
13176     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13177         return;
13178
13179
13180     if (gameMode == PlayFromGameFile ||
13181         gameMode == TwoMachinesPlay  ||
13182         gameMode == Training         ||
13183         gameMode == AnalyzeMode      ||
13184         gameMode == EndOfGame)
13185         EditGameEvent();
13186
13187     if (gameMode == EditPosition)
13188         EditPositionDone(TRUE);
13189
13190     if (WhiteOnMove(currentMove)) {
13191         DisplayError(_("It is not Black's turn"), 0);
13192         return;
13193     }
13194
13195     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13196       ExitAnalyzeMode();
13197
13198     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13199         gameMode == AnalyzeFile)
13200         TruncateGame();
13201
13202     ResurrectChessProgram();    /* in case it isn't running */
13203     gameMode = MachinePlaysBlack;
13204     pausing = FALSE;
13205     ModeHighlight();
13206     SetGameInfo();
13207     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13208     DisplayTitle(buf);
13209     if (first.sendName) {
13210       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13211       SendToProgram(buf, &first);
13212     }
13213     if (first.sendTime) {
13214       if (first.useColors) {
13215         SendToProgram("white\n", &first); /*gnu kludge*/
13216       }
13217       SendTimeRemaining(&first, FALSE);
13218     }
13219     if (first.useColors) {
13220       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13221     }
13222     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13223     SetMachineThinkingEnables();
13224     first.maybeThinking = TRUE;
13225     StartClocks();
13226
13227     if (appData.autoFlipView && flipView) {
13228       flipView = !flipView;
13229       DrawPosition(FALSE, NULL);
13230       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13231     }
13232     if(bookHit) { // [HGM] book: simulate book reply
13233         static char bookMove[MSG_SIZ]; // a bit generous?
13234
13235         programStats.nodes = programStats.depth = programStats.time =
13236         programStats.score = programStats.got_only_move = 0;
13237         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13238
13239         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13240         strcat(bookMove, bookHit);
13241         HandleMachineMove(bookMove, &first);
13242     }
13243 }
13244
13245
13246 void
13247 DisplayTwoMachinesTitle ()
13248 {
13249     char buf[MSG_SIZ];
13250     if (appData.matchGames > 0) {
13251         if(appData.tourneyFile[0]) {
13252           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13253                    gameInfo.white, _("vs."), gameInfo.black,
13254                    nextGame+1, appData.matchGames+1,
13255                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13256         } else 
13257         if (first.twoMachinesColor[0] == 'w') {
13258           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13259                    gameInfo.white, _("vs."),  gameInfo.black,
13260                    first.matchWins, second.matchWins,
13261                    matchGame - 1 - (first.matchWins + second.matchWins));
13262         } else {
13263           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13264                    gameInfo.white, _("vs."), gameInfo.black,
13265                    second.matchWins, first.matchWins,
13266                    matchGame - 1 - (first.matchWins + second.matchWins));
13267         }
13268     } else {
13269       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13270     }
13271     DisplayTitle(buf);
13272 }
13273
13274 void
13275 SettingsMenuIfReady ()
13276 {
13277   if (second.lastPing != second.lastPong) {
13278     DisplayMessage("", _("Waiting for second chess program"));
13279     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13280     return;
13281   }
13282   ThawUI();
13283   DisplayMessage("", "");
13284   SettingsPopUp(&second);
13285 }
13286
13287 int
13288 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13289 {
13290     char buf[MSG_SIZ];
13291     if (cps->pr == NoProc) {
13292         StartChessProgram(cps);
13293         if (cps->protocolVersion == 1) {
13294           retry();
13295         } else {
13296           /* kludge: allow timeout for initial "feature" command */
13297           FreezeUI();
13298           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13299           DisplayMessage("", buf);
13300           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13301         }
13302         return 1;
13303     }
13304     return 0;
13305 }
13306
13307 void
13308 TwoMachinesEvent P((void))
13309 {
13310     int i;
13311     char buf[MSG_SIZ];
13312     ChessProgramState *onmove;
13313     char *bookHit = NULL;
13314     static int stalling = 0;
13315     TimeMark now;
13316     long wait;
13317
13318     if (appData.noChessProgram) return;
13319
13320     switch (gameMode) {
13321       case TwoMachinesPlay:
13322         return;
13323       case MachinePlaysWhite:
13324       case MachinePlaysBlack:
13325         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13326             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13327             return;
13328         }
13329         /* fall through */
13330       case BeginningOfGame:
13331       case PlayFromGameFile:
13332       case EndOfGame:
13333         EditGameEvent();
13334         if (gameMode != EditGame) return;
13335         break;
13336       case EditPosition:
13337         EditPositionDone(TRUE);
13338         break;
13339       case AnalyzeMode:
13340       case AnalyzeFile:
13341         ExitAnalyzeMode();
13342         break;
13343       case EditGame:
13344       default:
13345         break;
13346     }
13347
13348 //    forwardMostMove = currentMove;
13349     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13350
13351     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13352
13353     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13354     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13355       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13356       return;
13357     }
13358     if(!stalling) {
13359       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13360       SendToProgram("force\n", &second);
13361       stalling = 1;
13362       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13363       return;
13364     }
13365     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13366     if(appData.matchPause>10000 || appData.matchPause<10)
13367                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13368     wait = SubtractTimeMarks(&now, &pauseStart);
13369     if(wait < appData.matchPause) {
13370         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13371         return;
13372     }
13373     stalling = 0;
13374     DisplayMessage("", "");
13375     if (startedFromSetupPosition) {
13376         SendBoard(&second, backwardMostMove);
13377     if (appData.debugMode) {
13378         fprintf(debugFP, "Two Machines\n");
13379     }
13380     }
13381     for (i = backwardMostMove; i < forwardMostMove; i++) {
13382         SendMoveToProgram(i, &second);
13383     }
13384
13385     gameMode = TwoMachinesPlay;
13386     pausing = FALSE;
13387     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13388     SetGameInfo();
13389     DisplayTwoMachinesTitle();
13390     firstMove = TRUE;
13391     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13392         onmove = &first;
13393     } else {
13394         onmove = &second;
13395     }
13396     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13397     SendToProgram(first.computerString, &first);
13398     if (first.sendName) {
13399       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13400       SendToProgram(buf, &first);
13401     }
13402     SendToProgram(second.computerString, &second);
13403     if (second.sendName) {
13404       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13405       SendToProgram(buf, &second);
13406     }
13407
13408     ResetClocks();
13409     if (!first.sendTime || !second.sendTime) {
13410         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13411         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13412     }
13413     if (onmove->sendTime) {
13414       if (onmove->useColors) {
13415         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13416       }
13417       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13418     }
13419     if (onmove->useColors) {
13420       SendToProgram(onmove->twoMachinesColor, onmove);
13421     }
13422     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13423 //    SendToProgram("go\n", onmove);
13424     onmove->maybeThinking = TRUE;
13425     SetMachineThinkingEnables();
13426
13427     StartClocks();
13428
13429     if(bookHit) { // [HGM] book: simulate book reply
13430         static char bookMove[MSG_SIZ]; // a bit generous?
13431
13432         programStats.nodes = programStats.depth = programStats.time =
13433         programStats.score = programStats.got_only_move = 0;
13434         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13435
13436         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13437         strcat(bookMove, bookHit);
13438         savedMessage = bookMove; // args for deferred call
13439         savedState = onmove;
13440         ScheduleDelayedEvent(DeferredBookMove, 1);
13441     }
13442 }
13443
13444 void
13445 TrainingEvent ()
13446 {
13447     if (gameMode == Training) {
13448       SetTrainingModeOff();
13449       gameMode = PlayFromGameFile;
13450       DisplayMessage("", _("Training mode off"));
13451     } else {
13452       gameMode = Training;
13453       animateTraining = appData.animate;
13454
13455       /* make sure we are not already at the end of the game */
13456       if (currentMove < forwardMostMove) {
13457         SetTrainingModeOn();
13458         DisplayMessage("", _("Training mode on"));
13459       } else {
13460         gameMode = PlayFromGameFile;
13461         DisplayError(_("Already at end of game"), 0);
13462       }
13463     }
13464     ModeHighlight();
13465 }
13466
13467 void
13468 IcsClientEvent ()
13469 {
13470     if (!appData.icsActive) return;
13471     switch (gameMode) {
13472       case IcsPlayingWhite:
13473       case IcsPlayingBlack:
13474       case IcsObserving:
13475       case IcsIdle:
13476       case BeginningOfGame:
13477       case IcsExamining:
13478         return;
13479
13480       case EditGame:
13481         break;
13482
13483       case EditPosition:
13484         EditPositionDone(TRUE);
13485         break;
13486
13487       case AnalyzeMode:
13488       case AnalyzeFile:
13489         ExitAnalyzeMode();
13490         break;
13491
13492       default:
13493         EditGameEvent();
13494         break;
13495     }
13496
13497     gameMode = IcsIdle;
13498     ModeHighlight();
13499     return;
13500 }
13501
13502 void
13503 EditGameEvent ()
13504 {
13505     int i;
13506
13507     switch (gameMode) {
13508       case Training:
13509         SetTrainingModeOff();
13510         break;
13511       case MachinePlaysWhite:
13512       case MachinePlaysBlack:
13513       case BeginningOfGame:
13514         SendToProgram("force\n", &first);
13515         SetUserThinkingEnables();
13516         break;
13517       case PlayFromGameFile:
13518         (void) StopLoadGameTimer();
13519         if (gameFileFP != NULL) {
13520             gameFileFP = NULL;
13521         }
13522         break;
13523       case EditPosition:
13524         EditPositionDone(TRUE);
13525         break;
13526       case AnalyzeMode:
13527       case AnalyzeFile:
13528         ExitAnalyzeMode();
13529         SendToProgram("force\n", &first);
13530         break;
13531       case TwoMachinesPlay:
13532         GameEnds(EndOfFile, NULL, GE_PLAYER);
13533         ResurrectChessProgram();
13534         SetUserThinkingEnables();
13535         break;
13536       case EndOfGame:
13537         ResurrectChessProgram();
13538         break;
13539       case IcsPlayingBlack:
13540       case IcsPlayingWhite:
13541         DisplayError(_("Warning: You are still playing a game"), 0);
13542         break;
13543       case IcsObserving:
13544         DisplayError(_("Warning: You are still observing a game"), 0);
13545         break;
13546       case IcsExamining:
13547         DisplayError(_("Warning: You are still examining a game"), 0);
13548         break;
13549       case IcsIdle:
13550         break;
13551       case EditGame:
13552       default:
13553         return;
13554     }
13555
13556     pausing = FALSE;
13557     StopClocks();
13558     first.offeredDraw = second.offeredDraw = 0;
13559
13560     if (gameMode == PlayFromGameFile) {
13561         whiteTimeRemaining = timeRemaining[0][currentMove];
13562         blackTimeRemaining = timeRemaining[1][currentMove];
13563         DisplayTitle("");
13564     }
13565
13566     if (gameMode == MachinePlaysWhite ||
13567         gameMode == MachinePlaysBlack ||
13568         gameMode == TwoMachinesPlay ||
13569         gameMode == EndOfGame) {
13570         i = forwardMostMove;
13571         while (i > currentMove) {
13572             SendToProgram("undo\n", &first);
13573             i--;
13574         }
13575         if(!adjustedClock) {
13576         whiteTimeRemaining = timeRemaining[0][currentMove];
13577         blackTimeRemaining = timeRemaining[1][currentMove];
13578         DisplayBothClocks();
13579         }
13580         if (whiteFlag || blackFlag) {
13581             whiteFlag = blackFlag = 0;
13582         }
13583         DisplayTitle("");
13584     }
13585
13586     gameMode = EditGame;
13587     ModeHighlight();
13588     SetGameInfo();
13589 }
13590
13591
13592 void
13593 EditPositionEvent ()
13594 {
13595     if (gameMode == EditPosition) {
13596         EditGameEvent();
13597         return;
13598     }
13599
13600     EditGameEvent();
13601     if (gameMode != EditGame) return;
13602
13603     gameMode = EditPosition;
13604     ModeHighlight();
13605     SetGameInfo();
13606     if (currentMove > 0)
13607       CopyBoard(boards[0], boards[currentMove]);
13608
13609     blackPlaysFirst = !WhiteOnMove(currentMove);
13610     ResetClocks();
13611     currentMove = forwardMostMove = backwardMostMove = 0;
13612     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13613     DisplayMove(-1);
13614 }
13615
13616 void
13617 ExitAnalyzeMode ()
13618 {
13619     /* [DM] icsEngineAnalyze - possible call from other functions */
13620     if (appData.icsEngineAnalyze) {
13621         appData.icsEngineAnalyze = FALSE;
13622
13623         DisplayMessage("",_("Close ICS engine analyze..."));
13624     }
13625     if (first.analysisSupport && first.analyzing) {
13626       SendToProgram("exit\n", &first);
13627       first.analyzing = FALSE;
13628     }
13629     thinkOutput[0] = NULLCHAR;
13630 }
13631
13632 void
13633 EditPositionDone (Boolean fakeRights)
13634 {
13635     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13636
13637     startedFromSetupPosition = TRUE;
13638     InitChessProgram(&first, FALSE);
13639     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13640       boards[0][EP_STATUS] = EP_NONE;
13641       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13642     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13643         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13644         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13645       } else boards[0][CASTLING][2] = NoRights;
13646     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13647         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13648         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13649       } else boards[0][CASTLING][5] = NoRights;
13650     }
13651     SendToProgram("force\n", &first);
13652     if (blackPlaysFirst) {
13653         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13654         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13655         currentMove = forwardMostMove = backwardMostMove = 1;
13656         CopyBoard(boards[1], boards[0]);
13657     } else {
13658         currentMove = forwardMostMove = backwardMostMove = 0;
13659     }
13660     SendBoard(&first, forwardMostMove);
13661     if (appData.debugMode) {
13662         fprintf(debugFP, "EditPosDone\n");
13663     }
13664     DisplayTitle("");
13665     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13666     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13667     gameMode = EditGame;
13668     ModeHighlight();
13669     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13670     ClearHighlights(); /* [AS] */
13671 }
13672
13673 /* Pause for `ms' milliseconds */
13674 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13675 void
13676 TimeDelay (long ms)
13677 {
13678     TimeMark m1, m2;
13679
13680     GetTimeMark(&m1);
13681     do {
13682         GetTimeMark(&m2);
13683     } while (SubtractTimeMarks(&m2, &m1) < ms);
13684 }
13685
13686 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13687 void
13688 SendMultiLineToICS (char *buf)
13689 {
13690     char temp[MSG_SIZ+1], *p;
13691     int len;
13692
13693     len = strlen(buf);
13694     if (len > MSG_SIZ)
13695       len = MSG_SIZ;
13696
13697     strncpy(temp, buf, len);
13698     temp[len] = 0;
13699
13700     p = temp;
13701     while (*p) {
13702         if (*p == '\n' || *p == '\r')
13703           *p = ' ';
13704         ++p;
13705     }
13706
13707     strcat(temp, "\n");
13708     SendToICS(temp);
13709     SendToPlayer(temp, strlen(temp));
13710 }
13711
13712 void
13713 SetWhiteToPlayEvent ()
13714 {
13715     if (gameMode == EditPosition) {
13716         blackPlaysFirst = FALSE;
13717         DisplayBothClocks();    /* works because currentMove is 0 */
13718     } else if (gameMode == IcsExamining) {
13719         SendToICS(ics_prefix);
13720         SendToICS("tomove white\n");
13721     }
13722 }
13723
13724 void
13725 SetBlackToPlayEvent ()
13726 {
13727     if (gameMode == EditPosition) {
13728         blackPlaysFirst = TRUE;
13729         currentMove = 1;        /* kludge */
13730         DisplayBothClocks();
13731         currentMove = 0;
13732     } else if (gameMode == IcsExamining) {
13733         SendToICS(ics_prefix);
13734         SendToICS("tomove black\n");
13735     }
13736 }
13737
13738 void
13739 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13740 {
13741     char buf[MSG_SIZ];
13742     ChessSquare piece = boards[0][y][x];
13743
13744     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13745
13746     switch (selection) {
13747       case ClearBoard:
13748         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13749             SendToICS(ics_prefix);
13750             SendToICS("bsetup clear\n");
13751         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13752             SendToICS(ics_prefix);
13753             SendToICS("clearboard\n");
13754         } else {
13755             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13756                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13757                 for (y = 0; y < BOARD_HEIGHT; y++) {
13758                     if (gameMode == IcsExamining) {
13759                         if (boards[currentMove][y][x] != EmptySquare) {
13760                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13761                                     AAA + x, ONE + y);
13762                             SendToICS(buf);
13763                         }
13764                     } else {
13765                         boards[0][y][x] = p;
13766                     }
13767                 }
13768             }
13769         }
13770         if (gameMode == EditPosition) {
13771             DrawPosition(FALSE, boards[0]);
13772         }
13773         break;
13774
13775       case WhitePlay:
13776         SetWhiteToPlayEvent();
13777         break;
13778
13779       case BlackPlay:
13780         SetBlackToPlayEvent();
13781         break;
13782
13783       case EmptySquare:
13784         if (gameMode == IcsExamining) {
13785             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13786             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13787             SendToICS(buf);
13788         } else {
13789             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13790                 if(x == BOARD_LEFT-2) {
13791                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13792                     boards[0][y][1] = 0;
13793                 } else
13794                 if(x == BOARD_RGHT+1) {
13795                     if(y >= gameInfo.holdingsSize) break;
13796                     boards[0][y][BOARD_WIDTH-2] = 0;
13797                 } else break;
13798             }
13799             boards[0][y][x] = EmptySquare;
13800             DrawPosition(FALSE, boards[0]);
13801         }
13802         break;
13803
13804       case PromotePiece:
13805         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13806            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13807             selection = (ChessSquare) (PROMOTED piece);
13808         } else if(piece == EmptySquare) selection = WhiteSilver;
13809         else selection = (ChessSquare)((int)piece - 1);
13810         goto defaultlabel;
13811
13812       case DemotePiece:
13813         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13814            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13815             selection = (ChessSquare) (DEMOTED piece);
13816         } else if(piece == EmptySquare) selection = BlackSilver;
13817         else selection = (ChessSquare)((int)piece + 1);
13818         goto defaultlabel;
13819
13820       case WhiteQueen:
13821       case BlackQueen:
13822         if(gameInfo.variant == VariantShatranj ||
13823            gameInfo.variant == VariantXiangqi  ||
13824            gameInfo.variant == VariantCourier  ||
13825            gameInfo.variant == VariantMakruk     )
13826             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13827         goto defaultlabel;
13828
13829       case WhiteKing:
13830       case BlackKing:
13831         if(gameInfo.variant == VariantXiangqi)
13832             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13833         if(gameInfo.variant == VariantKnightmate)
13834             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13835       default:
13836         defaultlabel:
13837         if (gameMode == IcsExamining) {
13838             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13839             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13840                      PieceToChar(selection), AAA + x, ONE + y);
13841             SendToICS(buf);
13842         } else {
13843             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13844                 int n;
13845                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13846                     n = PieceToNumber(selection - BlackPawn);
13847                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13848                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13849                     boards[0][BOARD_HEIGHT-1-n][1]++;
13850                 } else
13851                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13852                     n = PieceToNumber(selection);
13853                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13854                     boards[0][n][BOARD_WIDTH-1] = selection;
13855                     boards[0][n][BOARD_WIDTH-2]++;
13856                 }
13857             } else
13858             boards[0][y][x] = selection;
13859             DrawPosition(TRUE, boards[0]);
13860         }
13861         break;
13862     }
13863 }
13864
13865
13866 void
13867 DropMenuEvent (ChessSquare selection, int x, int y)
13868 {
13869     ChessMove moveType;
13870
13871     switch (gameMode) {
13872       case IcsPlayingWhite:
13873       case MachinePlaysBlack:
13874         if (!WhiteOnMove(currentMove)) {
13875             DisplayMoveError(_("It is Black's turn"));
13876             return;
13877         }
13878         moveType = WhiteDrop;
13879         break;
13880       case IcsPlayingBlack:
13881       case MachinePlaysWhite:
13882         if (WhiteOnMove(currentMove)) {
13883             DisplayMoveError(_("It is White's turn"));
13884             return;
13885         }
13886         moveType = BlackDrop;
13887         break;
13888       case EditGame:
13889         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13890         break;
13891       default:
13892         return;
13893     }
13894
13895     if (moveType == BlackDrop && selection < BlackPawn) {
13896       selection = (ChessSquare) ((int) selection
13897                                  + (int) BlackPawn - (int) WhitePawn);
13898     }
13899     if (boards[currentMove][y][x] != EmptySquare) {
13900         DisplayMoveError(_("That square is occupied"));
13901         return;
13902     }
13903
13904     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13905 }
13906
13907 void
13908 AcceptEvent ()
13909 {
13910     /* Accept a pending offer of any kind from opponent */
13911
13912     if (appData.icsActive) {
13913         SendToICS(ics_prefix);
13914         SendToICS("accept\n");
13915     } else if (cmailMsgLoaded) {
13916         if (currentMove == cmailOldMove &&
13917             commentList[cmailOldMove] != NULL &&
13918             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13919                    "Black offers a draw" : "White offers a draw")) {
13920             TruncateGame();
13921             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13922             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13923         } else {
13924             DisplayError(_("There is no pending offer on this move"), 0);
13925             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13926         }
13927     } else {
13928         /* Not used for offers from chess program */
13929     }
13930 }
13931
13932 void
13933 DeclineEvent ()
13934 {
13935     /* Decline a pending offer of any kind from opponent */
13936
13937     if (appData.icsActive) {
13938         SendToICS(ics_prefix);
13939         SendToICS("decline\n");
13940     } else if (cmailMsgLoaded) {
13941         if (currentMove == cmailOldMove &&
13942             commentList[cmailOldMove] != NULL &&
13943             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13944                    "Black offers a draw" : "White offers a draw")) {
13945 #ifdef NOTDEF
13946             AppendComment(cmailOldMove, "Draw declined", TRUE);
13947             DisplayComment(cmailOldMove - 1, "Draw declined");
13948 #endif /*NOTDEF*/
13949         } else {
13950             DisplayError(_("There is no pending offer on this move"), 0);
13951         }
13952     } else {
13953         /* Not used for offers from chess program */
13954     }
13955 }
13956
13957 void
13958 RematchEvent ()
13959 {
13960     /* Issue ICS rematch command */
13961     if (appData.icsActive) {
13962         SendToICS(ics_prefix);
13963         SendToICS("rematch\n");
13964     }
13965 }
13966
13967 void
13968 CallFlagEvent ()
13969 {
13970     /* Call your opponent's flag (claim a win on time) */
13971     if (appData.icsActive) {
13972         SendToICS(ics_prefix);
13973         SendToICS("flag\n");
13974     } else {
13975         switch (gameMode) {
13976           default:
13977             return;
13978           case MachinePlaysWhite:
13979             if (whiteFlag) {
13980                 if (blackFlag)
13981                   GameEnds(GameIsDrawn, "Both players ran out of time",
13982                            GE_PLAYER);
13983                 else
13984                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13985             } else {
13986                 DisplayError(_("Your opponent is not out of time"), 0);
13987             }
13988             break;
13989           case MachinePlaysBlack:
13990             if (blackFlag) {
13991                 if (whiteFlag)
13992                   GameEnds(GameIsDrawn, "Both players ran out of time",
13993                            GE_PLAYER);
13994                 else
13995                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13996             } else {
13997                 DisplayError(_("Your opponent is not out of time"), 0);
13998             }
13999             break;
14000         }
14001     }
14002 }
14003
14004 void
14005 ClockClick (int which)
14006 {       // [HGM] code moved to back-end from winboard.c
14007         if(which) { // black clock
14008           if (gameMode == EditPosition || gameMode == IcsExamining) {
14009             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14010             SetBlackToPlayEvent();
14011           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14012           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14013           } else if (shiftKey) {
14014             AdjustClock(which, -1);
14015           } else if (gameMode == IcsPlayingWhite ||
14016                      gameMode == MachinePlaysBlack) {
14017             CallFlagEvent();
14018           }
14019         } else { // white clock
14020           if (gameMode == EditPosition || gameMode == IcsExamining) {
14021             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14022             SetWhiteToPlayEvent();
14023           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14024           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14025           } else if (shiftKey) {
14026             AdjustClock(which, -1);
14027           } else if (gameMode == IcsPlayingBlack ||
14028                    gameMode == MachinePlaysWhite) {
14029             CallFlagEvent();
14030           }
14031         }
14032 }
14033
14034 void
14035 DrawEvent ()
14036 {
14037     /* Offer draw or accept pending draw offer from opponent */
14038
14039     if (appData.icsActive) {
14040         /* Note: tournament rules require draw offers to be
14041            made after you make your move but before you punch
14042            your clock.  Currently ICS doesn't let you do that;
14043            instead, you immediately punch your clock after making
14044            a move, but you can offer a draw at any time. */
14045
14046         SendToICS(ics_prefix);
14047         SendToICS("draw\n");
14048         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14049     } else if (cmailMsgLoaded) {
14050         if (currentMove == cmailOldMove &&
14051             commentList[cmailOldMove] != NULL &&
14052             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14053                    "Black offers a draw" : "White offers a draw")) {
14054             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14055             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14056         } else if (currentMove == cmailOldMove + 1) {
14057             char *offer = WhiteOnMove(cmailOldMove) ?
14058               "White offers a draw" : "Black offers a draw";
14059             AppendComment(currentMove, offer, TRUE);
14060             DisplayComment(currentMove - 1, offer);
14061             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14062         } else {
14063             DisplayError(_("You must make your move before offering a draw"), 0);
14064             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14065         }
14066     } else if (first.offeredDraw) {
14067         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14068     } else {
14069         if (first.sendDrawOffers) {
14070             SendToProgram("draw\n", &first);
14071             userOfferedDraw = TRUE;
14072         }
14073     }
14074 }
14075
14076 void
14077 AdjournEvent ()
14078 {
14079     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14080
14081     if (appData.icsActive) {
14082         SendToICS(ics_prefix);
14083         SendToICS("adjourn\n");
14084     } else {
14085         /* Currently GNU Chess doesn't offer or accept Adjourns */
14086     }
14087 }
14088
14089
14090 void
14091 AbortEvent ()
14092 {
14093     /* Offer Abort or accept pending Abort offer from opponent */
14094
14095     if (appData.icsActive) {
14096         SendToICS(ics_prefix);
14097         SendToICS("abort\n");
14098     } else {
14099         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14100     }
14101 }
14102
14103 void
14104 ResignEvent ()
14105 {
14106     /* Resign.  You can do this even if it's not your turn. */
14107
14108     if (appData.icsActive) {
14109         SendToICS(ics_prefix);
14110         SendToICS("resign\n");
14111     } else {
14112         switch (gameMode) {
14113           case MachinePlaysWhite:
14114             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14115             break;
14116           case MachinePlaysBlack:
14117             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14118             break;
14119           case EditGame:
14120             if (cmailMsgLoaded) {
14121                 TruncateGame();
14122                 if (WhiteOnMove(cmailOldMove)) {
14123                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14124                 } else {
14125                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14126                 }
14127                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14128             }
14129             break;
14130           default:
14131             break;
14132         }
14133     }
14134 }
14135
14136
14137 void
14138 StopObservingEvent ()
14139 {
14140     /* Stop observing current games */
14141     SendToICS(ics_prefix);
14142     SendToICS("unobserve\n");
14143 }
14144
14145 void
14146 StopExaminingEvent ()
14147 {
14148     /* Stop observing current game */
14149     SendToICS(ics_prefix);
14150     SendToICS("unexamine\n");
14151 }
14152
14153 void
14154 ForwardInner (int target)
14155 {
14156     int limit;
14157
14158     if (appData.debugMode)
14159         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14160                 target, currentMove, forwardMostMove);
14161
14162     if (gameMode == EditPosition)
14163       return;
14164
14165     seekGraphUp = FALSE;
14166     MarkTargetSquares(1);
14167
14168     if (gameMode == PlayFromGameFile && !pausing)
14169       PauseEvent();
14170
14171     if (gameMode == IcsExamining && pausing)
14172       limit = pauseExamForwardMostMove;
14173     else
14174       limit = forwardMostMove;
14175
14176     if (target > limit) target = limit;
14177
14178     if (target > 0 && moveList[target - 1][0]) {
14179         int fromX, fromY, toX, toY;
14180         toX = moveList[target - 1][2] - AAA;
14181         toY = moveList[target - 1][3] - ONE;
14182         if (moveList[target - 1][1] == '@') {
14183             if (appData.highlightLastMove) {
14184                 SetHighlights(-1, -1, toX, toY);
14185             }
14186         } else {
14187             fromX = moveList[target - 1][0] - AAA;
14188             fromY = moveList[target - 1][1] - ONE;
14189             if (target == currentMove + 1) {
14190                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14191             }
14192             if (appData.highlightLastMove) {
14193                 SetHighlights(fromX, fromY, toX, toY);
14194             }
14195         }
14196     }
14197     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14198         gameMode == Training || gameMode == PlayFromGameFile ||
14199         gameMode == AnalyzeFile) {
14200         while (currentMove < target) {
14201             SendMoveToProgram(currentMove++, &first);
14202         }
14203     } else {
14204         currentMove = target;
14205     }
14206
14207     if (gameMode == EditGame || gameMode == EndOfGame) {
14208         whiteTimeRemaining = timeRemaining[0][currentMove];
14209         blackTimeRemaining = timeRemaining[1][currentMove];
14210     }
14211     DisplayBothClocks();
14212     DisplayMove(currentMove - 1);
14213     DrawPosition(FALSE, boards[currentMove]);
14214     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14215     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14216         DisplayComment(currentMove - 1, commentList[currentMove]);
14217     }
14218 }
14219
14220
14221 void
14222 ForwardEvent ()
14223 {
14224     if (gameMode == IcsExamining && !pausing) {
14225         SendToICS(ics_prefix);
14226         SendToICS("forward\n");
14227     } else {
14228         ForwardInner(currentMove + 1);
14229     }
14230 }
14231
14232 void
14233 ToEndEvent ()
14234 {
14235     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14236         /* to optimze, we temporarily turn off analysis mode while we feed
14237          * the remaining moves to the engine. Otherwise we get analysis output
14238          * after each move.
14239          */
14240         if (first.analysisSupport) {
14241           SendToProgram("exit\nforce\n", &first);
14242           first.analyzing = FALSE;
14243         }
14244     }
14245
14246     if (gameMode == IcsExamining && !pausing) {
14247         SendToICS(ics_prefix);
14248         SendToICS("forward 999999\n");
14249     } else {
14250         ForwardInner(forwardMostMove);
14251     }
14252
14253     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14254         /* we have fed all the moves, so reactivate analysis mode */
14255         SendToProgram("analyze\n", &first);
14256         first.analyzing = TRUE;
14257         /*first.maybeThinking = TRUE;*/
14258         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14259     }
14260 }
14261
14262 void
14263 BackwardInner (int target)
14264 {
14265     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14266
14267     if (appData.debugMode)
14268         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14269                 target, currentMove, forwardMostMove);
14270
14271     if (gameMode == EditPosition) return;
14272     seekGraphUp = FALSE;
14273     MarkTargetSquares(1);
14274     if (currentMove <= backwardMostMove) {
14275         ClearHighlights();
14276         DrawPosition(full_redraw, boards[currentMove]);
14277         return;
14278     }
14279     if (gameMode == PlayFromGameFile && !pausing)
14280       PauseEvent();
14281
14282     if (moveList[target][0]) {
14283         int fromX, fromY, toX, toY;
14284         toX = moveList[target][2] - AAA;
14285         toY = moveList[target][3] - ONE;
14286         if (moveList[target][1] == '@') {
14287             if (appData.highlightLastMove) {
14288                 SetHighlights(-1, -1, toX, toY);
14289             }
14290         } else {
14291             fromX = moveList[target][0] - AAA;
14292             fromY = moveList[target][1] - ONE;
14293             if (target == currentMove - 1) {
14294                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14295             }
14296             if (appData.highlightLastMove) {
14297                 SetHighlights(fromX, fromY, toX, toY);
14298             }
14299         }
14300     }
14301     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14302         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14303         while (currentMove > target) {
14304             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14305                 // null move cannot be undone. Reload program with move history before it.
14306                 int i;
14307                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14308                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14309                 }
14310                 SendBoard(&first, i); 
14311                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14312                 break;
14313             }
14314             SendToProgram("undo\n", &first);
14315             currentMove--;
14316         }
14317     } else {
14318         currentMove = target;
14319     }
14320
14321     if (gameMode == EditGame || gameMode == EndOfGame) {
14322         whiteTimeRemaining = timeRemaining[0][currentMove];
14323         blackTimeRemaining = timeRemaining[1][currentMove];
14324     }
14325     DisplayBothClocks();
14326     DisplayMove(currentMove - 1);
14327     DrawPosition(full_redraw, boards[currentMove]);
14328     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14329     // [HGM] PV info: routine tests if comment empty
14330     DisplayComment(currentMove - 1, commentList[currentMove]);
14331 }
14332
14333 void
14334 BackwardEvent ()
14335 {
14336     if (gameMode == IcsExamining && !pausing) {
14337         SendToICS(ics_prefix);
14338         SendToICS("backward\n");
14339     } else {
14340         BackwardInner(currentMove - 1);
14341     }
14342 }
14343
14344 void
14345 ToStartEvent ()
14346 {
14347     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14348         /* to optimize, we temporarily turn off analysis mode while we undo
14349          * all the moves. Otherwise we get analysis output after each undo.
14350          */
14351         if (first.analysisSupport) {
14352           SendToProgram("exit\nforce\n", &first);
14353           first.analyzing = FALSE;
14354         }
14355     }
14356
14357     if (gameMode == IcsExamining && !pausing) {
14358         SendToICS(ics_prefix);
14359         SendToICS("backward 999999\n");
14360     } else {
14361         BackwardInner(backwardMostMove);
14362     }
14363
14364     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14365         /* we have fed all the moves, so reactivate analysis mode */
14366         SendToProgram("analyze\n", &first);
14367         first.analyzing = TRUE;
14368         /*first.maybeThinking = TRUE;*/
14369         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14370     }
14371 }
14372
14373 void
14374 ToNrEvent (int to)
14375 {
14376   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14377   if (to >= forwardMostMove) to = forwardMostMove;
14378   if (to <= backwardMostMove) to = backwardMostMove;
14379   if (to < currentMove) {
14380     BackwardInner(to);
14381   } else {
14382     ForwardInner(to);
14383   }
14384 }
14385
14386 void
14387 RevertEvent (Boolean annotate)
14388 {
14389     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14390         return;
14391     }
14392     if (gameMode != IcsExamining) {
14393         DisplayError(_("You are not examining a game"), 0);
14394         return;
14395     }
14396     if (pausing) {
14397         DisplayError(_("You can't revert while pausing"), 0);
14398         return;
14399     }
14400     SendToICS(ics_prefix);
14401     SendToICS("revert\n");
14402 }
14403
14404 void
14405 RetractMoveEvent ()
14406 {
14407     switch (gameMode) {
14408       case MachinePlaysWhite:
14409       case MachinePlaysBlack:
14410         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14411             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14412             return;
14413         }
14414         if (forwardMostMove < 2) return;
14415         currentMove = forwardMostMove = forwardMostMove - 2;
14416         whiteTimeRemaining = timeRemaining[0][currentMove];
14417         blackTimeRemaining = timeRemaining[1][currentMove];
14418         DisplayBothClocks();
14419         DisplayMove(currentMove - 1);
14420         ClearHighlights();/*!! could figure this out*/
14421         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14422         SendToProgram("remove\n", &first);
14423         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14424         break;
14425
14426       case BeginningOfGame:
14427       default:
14428         break;
14429
14430       case IcsPlayingWhite:
14431       case IcsPlayingBlack:
14432         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14433             SendToICS(ics_prefix);
14434             SendToICS("takeback 2\n");
14435         } else {
14436             SendToICS(ics_prefix);
14437             SendToICS("takeback 1\n");
14438         }
14439         break;
14440     }
14441 }
14442
14443 void
14444 MoveNowEvent ()
14445 {
14446     ChessProgramState *cps;
14447
14448     switch (gameMode) {
14449       case MachinePlaysWhite:
14450         if (!WhiteOnMove(forwardMostMove)) {
14451             DisplayError(_("It is your turn"), 0);
14452             return;
14453         }
14454         cps = &first;
14455         break;
14456       case MachinePlaysBlack:
14457         if (WhiteOnMove(forwardMostMove)) {
14458             DisplayError(_("It is your turn"), 0);
14459             return;
14460         }
14461         cps = &first;
14462         break;
14463       case TwoMachinesPlay:
14464         if (WhiteOnMove(forwardMostMove) ==
14465             (first.twoMachinesColor[0] == 'w')) {
14466             cps = &first;
14467         } else {
14468             cps = &second;
14469         }
14470         break;
14471       case BeginningOfGame:
14472       default:
14473         return;
14474     }
14475     SendToProgram("?\n", cps);
14476 }
14477
14478 void
14479 TruncateGameEvent ()
14480 {
14481     EditGameEvent();
14482     if (gameMode != EditGame) return;
14483     TruncateGame();
14484 }
14485
14486 void
14487 TruncateGame ()
14488 {
14489     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14490     if (forwardMostMove > currentMove) {
14491         if (gameInfo.resultDetails != NULL) {
14492             free(gameInfo.resultDetails);
14493             gameInfo.resultDetails = NULL;
14494             gameInfo.result = GameUnfinished;
14495         }
14496         forwardMostMove = currentMove;
14497         HistorySet(parseList, backwardMostMove, forwardMostMove,
14498                    currentMove-1);
14499     }
14500 }
14501
14502 void
14503 HintEvent ()
14504 {
14505     if (appData.noChessProgram) return;
14506     switch (gameMode) {
14507       case MachinePlaysWhite:
14508         if (WhiteOnMove(forwardMostMove)) {
14509             DisplayError(_("Wait until your turn"), 0);
14510             return;
14511         }
14512         break;
14513       case BeginningOfGame:
14514       case MachinePlaysBlack:
14515         if (!WhiteOnMove(forwardMostMove)) {
14516             DisplayError(_("Wait until your turn"), 0);
14517             return;
14518         }
14519         break;
14520       default:
14521         DisplayError(_("No hint available"), 0);
14522         return;
14523     }
14524     SendToProgram("hint\n", &first);
14525     hintRequested = TRUE;
14526 }
14527
14528 void
14529 BookEvent ()
14530 {
14531     if (appData.noChessProgram) return;
14532     switch (gameMode) {
14533       case MachinePlaysWhite:
14534         if (WhiteOnMove(forwardMostMove)) {
14535             DisplayError(_("Wait until your turn"), 0);
14536             return;
14537         }
14538         break;
14539       case BeginningOfGame:
14540       case MachinePlaysBlack:
14541         if (!WhiteOnMove(forwardMostMove)) {
14542             DisplayError(_("Wait until your turn"), 0);
14543             return;
14544         }
14545         break;
14546       case EditPosition:
14547         EditPositionDone(TRUE);
14548         break;
14549       case TwoMachinesPlay:
14550         return;
14551       default:
14552         break;
14553     }
14554     SendToProgram("bk\n", &first);
14555     bookOutput[0] = NULLCHAR;
14556     bookRequested = TRUE;
14557 }
14558
14559 void
14560 AboutGameEvent ()
14561 {
14562     char *tags = PGNTags(&gameInfo);
14563     TagsPopUp(tags, CmailMsg());
14564     free(tags);
14565 }
14566
14567 /* end button procedures */
14568
14569 void
14570 PrintPosition (FILE *fp, int move)
14571 {
14572     int i, j;
14573
14574     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14575         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14576             char c = PieceToChar(boards[move][i][j]);
14577             fputc(c == 'x' ? '.' : c, fp);
14578             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14579         }
14580     }
14581     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14582       fprintf(fp, "white to play\n");
14583     else
14584       fprintf(fp, "black to play\n");
14585 }
14586
14587 void
14588 PrintOpponents (FILE *fp)
14589 {
14590     if (gameInfo.white != NULL) {
14591         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14592     } else {
14593         fprintf(fp, "\n");
14594     }
14595 }
14596
14597 /* Find last component of program's own name, using some heuristics */
14598 void
14599 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14600 {
14601     char *p, *q;
14602     int local = (strcmp(host, "localhost") == 0);
14603     while (!local && (p = strchr(prog, ';')) != NULL) {
14604         p++;
14605         while (*p == ' ') p++;
14606         prog = p;
14607     }
14608     if (*prog == '"' || *prog == '\'') {
14609         q = strchr(prog + 1, *prog);
14610     } else {
14611         q = strchr(prog, ' ');
14612     }
14613     if (q == NULL) q = prog + strlen(prog);
14614     p = q;
14615     while (p >= prog && *p != '/' && *p != '\\') p--;
14616     p++;
14617     if(p == prog && *p == '"') p++;
14618     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14619     memcpy(buf, p, q - p);
14620     buf[q - p] = NULLCHAR;
14621     if (!local) {
14622         strcat(buf, "@");
14623         strcat(buf, host);
14624     }
14625 }
14626
14627 char *
14628 TimeControlTagValue ()
14629 {
14630     char buf[MSG_SIZ];
14631     if (!appData.clockMode) {
14632       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14633     } else if (movesPerSession > 0) {
14634       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14635     } else if (timeIncrement == 0) {
14636       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14637     } else {
14638       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14639     }
14640     return StrSave(buf);
14641 }
14642
14643 void
14644 SetGameInfo ()
14645 {
14646     /* This routine is used only for certain modes */
14647     VariantClass v = gameInfo.variant;
14648     ChessMove r = GameUnfinished;
14649     char *p = NULL;
14650
14651     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14652         r = gameInfo.result;
14653         p = gameInfo.resultDetails;
14654         gameInfo.resultDetails = NULL;
14655     }
14656     ClearGameInfo(&gameInfo);
14657     gameInfo.variant = v;
14658
14659     switch (gameMode) {
14660       case MachinePlaysWhite:
14661         gameInfo.event = StrSave( appData.pgnEventHeader );
14662         gameInfo.site = StrSave(HostName());
14663         gameInfo.date = PGNDate();
14664         gameInfo.round = StrSave("-");
14665         gameInfo.white = StrSave(first.tidy);
14666         gameInfo.black = StrSave(UserName());
14667         gameInfo.timeControl = TimeControlTagValue();
14668         break;
14669
14670       case MachinePlaysBlack:
14671         gameInfo.event = StrSave( appData.pgnEventHeader );
14672         gameInfo.site = StrSave(HostName());
14673         gameInfo.date = PGNDate();
14674         gameInfo.round = StrSave("-");
14675         gameInfo.white = StrSave(UserName());
14676         gameInfo.black = StrSave(first.tidy);
14677         gameInfo.timeControl = TimeControlTagValue();
14678         break;
14679
14680       case TwoMachinesPlay:
14681         gameInfo.event = StrSave( appData.pgnEventHeader );
14682         gameInfo.site = StrSave(HostName());
14683         gameInfo.date = PGNDate();
14684         if (roundNr > 0) {
14685             char buf[MSG_SIZ];
14686             snprintf(buf, MSG_SIZ, "%d", roundNr);
14687             gameInfo.round = StrSave(buf);
14688         } else {
14689             gameInfo.round = StrSave("-");
14690         }
14691         if (first.twoMachinesColor[0] == 'w') {
14692             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14693             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14694         } else {
14695             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14696             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14697         }
14698         gameInfo.timeControl = TimeControlTagValue();
14699         break;
14700
14701       case EditGame:
14702         gameInfo.event = StrSave("Edited game");
14703         gameInfo.site = StrSave(HostName());
14704         gameInfo.date = PGNDate();
14705         gameInfo.round = StrSave("-");
14706         gameInfo.white = StrSave("-");
14707         gameInfo.black = StrSave("-");
14708         gameInfo.result = r;
14709         gameInfo.resultDetails = p;
14710         break;
14711
14712       case EditPosition:
14713         gameInfo.event = StrSave("Edited position");
14714         gameInfo.site = StrSave(HostName());
14715         gameInfo.date = PGNDate();
14716         gameInfo.round = StrSave("-");
14717         gameInfo.white = StrSave("-");
14718         gameInfo.black = StrSave("-");
14719         break;
14720
14721       case IcsPlayingWhite:
14722       case IcsPlayingBlack:
14723       case IcsObserving:
14724       case IcsExamining:
14725         break;
14726
14727       case PlayFromGameFile:
14728         gameInfo.event = StrSave("Game from non-PGN file");
14729         gameInfo.site = StrSave(HostName());
14730         gameInfo.date = PGNDate();
14731         gameInfo.round = StrSave("-");
14732         gameInfo.white = StrSave("?");
14733         gameInfo.black = StrSave("?");
14734         break;
14735
14736       default:
14737         break;
14738     }
14739 }
14740
14741 void
14742 ReplaceComment (int index, char *text)
14743 {
14744     int len;
14745     char *p;
14746     float score;
14747
14748     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14749        pvInfoList[index-1].depth == len &&
14750        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14751        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14752     while (*text == '\n') text++;
14753     len = strlen(text);
14754     while (len > 0 && text[len - 1] == '\n') len--;
14755
14756     if (commentList[index] != NULL)
14757       free(commentList[index]);
14758
14759     if (len == 0) {
14760         commentList[index] = NULL;
14761         return;
14762     }
14763   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14764       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14765       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14766     commentList[index] = (char *) malloc(len + 2);
14767     strncpy(commentList[index], text, len);
14768     commentList[index][len] = '\n';
14769     commentList[index][len + 1] = NULLCHAR;
14770   } else {
14771     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14772     char *p;
14773     commentList[index] = (char *) malloc(len + 7);
14774     safeStrCpy(commentList[index], "{\n", 3);
14775     safeStrCpy(commentList[index]+2, text, len+1);
14776     commentList[index][len+2] = NULLCHAR;
14777     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14778     strcat(commentList[index], "\n}\n");
14779   }
14780 }
14781
14782 void
14783 CrushCRs (char *text)
14784 {
14785   char *p = text;
14786   char *q = text;
14787   char ch;
14788
14789   do {
14790     ch = *p++;
14791     if (ch == '\r') continue;
14792     *q++ = ch;
14793   } while (ch != '\0');
14794 }
14795
14796 void
14797 AppendComment (int index, char *text, Boolean addBraces)
14798 /* addBraces  tells if we should add {} */
14799 {
14800     int oldlen, len;
14801     char *old;
14802
14803 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14804     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14805
14806     CrushCRs(text);
14807     while (*text == '\n') text++;
14808     len = strlen(text);
14809     while (len > 0 && text[len - 1] == '\n') len--;
14810     text[len] = NULLCHAR;
14811
14812     if (len == 0) return;
14813
14814     if (commentList[index] != NULL) {
14815       Boolean addClosingBrace = addBraces;
14816         old = commentList[index];
14817         oldlen = strlen(old);
14818         while(commentList[index][oldlen-1] ==  '\n')
14819           commentList[index][--oldlen] = NULLCHAR;
14820         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14821         safeStrCpy(commentList[index], old, oldlen + len + 6);
14822         free(old);
14823         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14824         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14825           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14826           while (*text == '\n') { text++; len--; }
14827           commentList[index][--oldlen] = NULLCHAR;
14828       }
14829         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14830         else          strcat(commentList[index], "\n");
14831         strcat(commentList[index], text);
14832         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14833         else          strcat(commentList[index], "\n");
14834     } else {
14835         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14836         if(addBraces)
14837           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14838         else commentList[index][0] = NULLCHAR;
14839         strcat(commentList[index], text);
14840         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14841         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14842     }
14843 }
14844
14845 static char *
14846 FindStr (char * text, char * sub_text)
14847 {
14848     char * result = strstr( text, sub_text );
14849
14850     if( result != NULL ) {
14851         result += strlen( sub_text );
14852     }
14853
14854     return result;
14855 }
14856
14857 /* [AS] Try to extract PV info from PGN comment */
14858 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14859 char *
14860 GetInfoFromComment (int index, char * text)
14861 {
14862     char * sep = text, *p;
14863
14864     if( text != NULL && index > 0 ) {
14865         int score = 0;
14866         int depth = 0;
14867         int time = -1, sec = 0, deci;
14868         char * s_eval = FindStr( text, "[%eval " );
14869         char * s_emt = FindStr( text, "[%emt " );
14870
14871         if( s_eval != NULL || s_emt != NULL ) {
14872             /* New style */
14873             char delim;
14874
14875             if( s_eval != NULL ) {
14876                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14877                     return text;
14878                 }
14879
14880                 if( delim != ']' ) {
14881                     return text;
14882                 }
14883             }
14884
14885             if( s_emt != NULL ) {
14886             }
14887                 return text;
14888         }
14889         else {
14890             /* We expect something like: [+|-]nnn.nn/dd */
14891             int score_lo = 0;
14892
14893             if(*text != '{') return text; // [HGM] braces: must be normal comment
14894
14895             sep = strchr( text, '/' );
14896             if( sep == NULL || sep < (text+4) ) {
14897                 return text;
14898             }
14899
14900             p = text;
14901             if(p[1] == '(') { // comment starts with PV
14902                p = strchr(p, ')'); // locate end of PV
14903                if(p == NULL || sep < p+5) return text;
14904                // at this point we have something like "{(.*) +0.23/6 ..."
14905                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14906                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14907                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14908             }
14909             time = -1; sec = -1; deci = -1;
14910             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14911                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14912                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14913                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14914                 return text;
14915             }
14916
14917             if( score_lo < 0 || score_lo >= 100 ) {
14918                 return text;
14919             }
14920
14921             if(sec >= 0) time = 600*time + 10*sec; else
14922             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14923
14924             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14925
14926             /* [HGM] PV time: now locate end of PV info */
14927             while( *++sep >= '0' && *sep <= '9'); // strip depth
14928             if(time >= 0)
14929             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14930             if(sec >= 0)
14931             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14932             if(deci >= 0)
14933             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14934             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
14935         }
14936
14937         if( depth <= 0 ) {
14938             return text;
14939         }
14940
14941         if( time < 0 ) {
14942             time = -1;
14943         }
14944
14945         pvInfoList[index-1].depth = depth;
14946         pvInfoList[index-1].score = score;
14947         pvInfoList[index-1].time  = 10*time; // centi-sec
14948         if(*sep == '}') *sep = 0; else *--sep = '{';
14949         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14950     }
14951     return sep;
14952 }
14953
14954 void
14955 SendToProgram (char *message, ChessProgramState *cps)
14956 {
14957     int count, outCount, error;
14958     char buf[MSG_SIZ];
14959
14960     if (cps->pr == NoProc) return;
14961     Attention(cps);
14962
14963     if (appData.debugMode) {
14964         TimeMark now;
14965         GetTimeMark(&now);
14966         fprintf(debugFP, "%ld >%-6s: %s",
14967                 SubtractTimeMarks(&now, &programStartTime),
14968                 cps->which, message);
14969     }
14970
14971     count = strlen(message);
14972     outCount = OutputToProcess(cps->pr, message, count, &error);
14973     if (outCount < count && !exiting
14974                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14975       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14976       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14977         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14978             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14979                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14980                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14981                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14982             } else {
14983                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14984                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14985                 gameInfo.result = res;
14986             }
14987             gameInfo.resultDetails = StrSave(buf);
14988         }
14989         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14990         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14991     }
14992 }
14993
14994 void
14995 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
14996 {
14997     char *end_str;
14998     char buf[MSG_SIZ];
14999     ChessProgramState *cps = (ChessProgramState *)closure;
15000
15001     if (isr != cps->isr) return; /* Killed intentionally */
15002     if (count <= 0) {
15003         if (count == 0) {
15004             RemoveInputSource(cps->isr);
15005             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15006             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15007                     _(cps->which), cps->program);
15008         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15009                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15010                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15011                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15012                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15013                 } else {
15014                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15015                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15016                     gameInfo.result = res;
15017                 }
15018                 gameInfo.resultDetails = StrSave(buf);
15019             }
15020             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15021             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15022         } else {
15023             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15024                     _(cps->which), cps->program);
15025             RemoveInputSource(cps->isr);
15026
15027             /* [AS] Program is misbehaving badly... kill it */
15028             if( count == -2 ) {
15029                 DestroyChildProcess( cps->pr, 9 );
15030                 cps->pr = NoProc;
15031             }
15032
15033             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15034         }
15035         return;
15036     }
15037
15038     if ((end_str = strchr(message, '\r')) != NULL)
15039       *end_str = NULLCHAR;
15040     if ((end_str = strchr(message, '\n')) != NULL)
15041       *end_str = NULLCHAR;
15042
15043     if (appData.debugMode) {
15044         TimeMark now; int print = 1;
15045         char *quote = ""; char c; int i;
15046
15047         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15048                 char start = message[0];
15049                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15050                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15051                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15052                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15053                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15054                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15055                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15056                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15057                    sscanf(message, "hint: %c", &c)!=1 && 
15058                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15059                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15060                     print = (appData.engineComments >= 2);
15061                 }
15062                 message[0] = start; // restore original message
15063         }
15064         if(print) {
15065                 GetTimeMark(&now);
15066                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15067                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15068                         quote,
15069                         message);
15070         }
15071     }
15072
15073     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15074     if (appData.icsEngineAnalyze) {
15075         if (strstr(message, "whisper") != NULL ||
15076              strstr(message, "kibitz") != NULL ||
15077             strstr(message, "tellics") != NULL) return;
15078     }
15079
15080     HandleMachineMove(message, cps);
15081 }
15082
15083
15084 void
15085 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15086 {
15087     char buf[MSG_SIZ];
15088     int seconds;
15089
15090     if( timeControl_2 > 0 ) {
15091         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15092             tc = timeControl_2;
15093         }
15094     }
15095     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15096     inc /= cps->timeOdds;
15097     st  /= cps->timeOdds;
15098
15099     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15100
15101     if (st > 0) {
15102       /* Set exact time per move, normally using st command */
15103       if (cps->stKludge) {
15104         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15105         seconds = st % 60;
15106         if (seconds == 0) {
15107           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15108         } else {
15109           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15110         }
15111       } else {
15112         snprintf(buf, MSG_SIZ, "st %d\n", st);
15113       }
15114     } else {
15115       /* Set conventional or incremental time control, using level command */
15116       if (seconds == 0) {
15117         /* Note old gnuchess bug -- minutes:seconds used to not work.
15118            Fixed in later versions, but still avoid :seconds
15119            when seconds is 0. */
15120         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15121       } else {
15122         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15123                  seconds, inc/1000.);
15124       }
15125     }
15126     SendToProgram(buf, cps);
15127
15128     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15129     /* Orthogonally, limit search to given depth */
15130     if (sd > 0) {
15131       if (cps->sdKludge) {
15132         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15133       } else {
15134         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15135       }
15136       SendToProgram(buf, cps);
15137     }
15138
15139     if(cps->nps >= 0) { /* [HGM] nps */
15140         if(cps->supportsNPS == FALSE)
15141           cps->nps = -1; // don't use if engine explicitly says not supported!
15142         else {
15143           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15144           SendToProgram(buf, cps);
15145         }
15146     }
15147 }
15148
15149 ChessProgramState *
15150 WhitePlayer ()
15151 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15152 {
15153     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15154        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15155         return &second;
15156     return &first;
15157 }
15158
15159 void
15160 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15161 {
15162     char message[MSG_SIZ];
15163     long time, otime;
15164
15165     /* Note: this routine must be called when the clocks are stopped
15166        or when they have *just* been set or switched; otherwise
15167        it will be off by the time since the current tick started.
15168     */
15169     if (machineWhite) {
15170         time = whiteTimeRemaining / 10;
15171         otime = blackTimeRemaining / 10;
15172     } else {
15173         time = blackTimeRemaining / 10;
15174         otime = whiteTimeRemaining / 10;
15175     }
15176     /* [HGM] translate opponent's time by time-odds factor */
15177     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15178     if (appData.debugMode) {
15179         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15180     }
15181
15182     if (time <= 0) time = 1;
15183     if (otime <= 0) otime = 1;
15184
15185     snprintf(message, MSG_SIZ, "time %ld\n", time);
15186     SendToProgram(message, cps);
15187
15188     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15189     SendToProgram(message, cps);
15190 }
15191
15192 int
15193 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15194 {
15195   char buf[MSG_SIZ];
15196   int len = strlen(name);
15197   int val;
15198
15199   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15200     (*p) += len + 1;
15201     sscanf(*p, "%d", &val);
15202     *loc = (val != 0);
15203     while (**p && **p != ' ')
15204       (*p)++;
15205     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15206     SendToProgram(buf, cps);
15207     return TRUE;
15208   }
15209   return FALSE;
15210 }
15211
15212 int
15213 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15214 {
15215   char buf[MSG_SIZ];
15216   int len = strlen(name);
15217   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15218     (*p) += len + 1;
15219     sscanf(*p, "%d", loc);
15220     while (**p && **p != ' ') (*p)++;
15221     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15222     SendToProgram(buf, cps);
15223     return TRUE;
15224   }
15225   return FALSE;
15226 }
15227
15228 int
15229 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15230 {
15231   char buf[MSG_SIZ];
15232   int len = strlen(name);
15233   if (strncmp((*p), name, len) == 0
15234       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15235     (*p) += len + 2;
15236     sscanf(*p, "%[^\"]", loc);
15237     while (**p && **p != '\"') (*p)++;
15238     if (**p == '\"') (*p)++;
15239     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15240     SendToProgram(buf, cps);
15241     return TRUE;
15242   }
15243   return FALSE;
15244 }
15245
15246 int
15247 ParseOption (Option *opt, ChessProgramState *cps)
15248 // [HGM] options: process the string that defines an engine option, and determine
15249 // name, type, default value, and allowed value range
15250 {
15251         char *p, *q, buf[MSG_SIZ];
15252         int n, min = (-1)<<31, max = 1<<31, def;
15253
15254         if(p = strstr(opt->name, " -spin ")) {
15255             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15256             if(max < min) max = min; // enforce consistency
15257             if(def < min) def = min;
15258             if(def > max) def = max;
15259             opt->value = def;
15260             opt->min = min;
15261             opt->max = max;
15262             opt->type = Spin;
15263         } else if((p = strstr(opt->name, " -slider "))) {
15264             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15265             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15266             if(max < min) max = min; // enforce consistency
15267             if(def < min) def = min;
15268             if(def > max) def = max;
15269             opt->value = def;
15270             opt->min = min;
15271             opt->max = max;
15272             opt->type = Spin; // Slider;
15273         } else if((p = strstr(opt->name, " -string "))) {
15274             opt->textValue = p+9;
15275             opt->type = TextBox;
15276         } else if((p = strstr(opt->name, " -file "))) {
15277             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15278             opt->textValue = p+7;
15279             opt->type = FileName; // FileName;
15280         } else if((p = strstr(opt->name, " -path "))) {
15281             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15282             opt->textValue = p+7;
15283             opt->type = PathName; // PathName;
15284         } else if(p = strstr(opt->name, " -check ")) {
15285             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15286             opt->value = (def != 0);
15287             opt->type = CheckBox;
15288         } else if(p = strstr(opt->name, " -combo ")) {
15289             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15290             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15291             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15292             opt->value = n = 0;
15293             while(q = StrStr(q, " /// ")) {
15294                 n++; *q = 0;    // count choices, and null-terminate each of them
15295                 q += 5;
15296                 if(*q == '*') { // remember default, which is marked with * prefix
15297                     q++;
15298                     opt->value = n;
15299                 }
15300                 cps->comboList[cps->comboCnt++] = q;
15301             }
15302             cps->comboList[cps->comboCnt++] = NULL;
15303             opt->max = n + 1;
15304             opt->type = ComboBox;
15305         } else if(p = strstr(opt->name, " -button")) {
15306             opt->type = Button;
15307         } else if(p = strstr(opt->name, " -save")) {
15308             opt->type = SaveButton;
15309         } else return FALSE;
15310         *p = 0; // terminate option name
15311         // now look if the command-line options define a setting for this engine option.
15312         if(cps->optionSettings && cps->optionSettings[0])
15313             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15314         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15315           snprintf(buf, MSG_SIZ, "option %s", p);
15316                 if(p = strstr(buf, ",")) *p = 0;
15317                 if(q = strchr(buf, '=')) switch(opt->type) {
15318                     case ComboBox:
15319                         for(n=0; n<opt->max; n++)
15320                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15321                         break;
15322                     case TextBox:
15323                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15324                         break;
15325                     case Spin:
15326                     case CheckBox:
15327                         opt->value = atoi(q+1);
15328                     default:
15329                         break;
15330                 }
15331                 strcat(buf, "\n");
15332                 SendToProgram(buf, cps);
15333         }
15334         return TRUE;
15335 }
15336
15337 void
15338 FeatureDone (ChessProgramState *cps, int val)
15339 {
15340   DelayedEventCallback cb = GetDelayedEvent();
15341   if ((cb == InitBackEnd3 && cps == &first) ||
15342       (cb == SettingsMenuIfReady && cps == &second) ||
15343       (cb == LoadEngine) ||
15344       (cb == TwoMachinesEventIfReady)) {
15345     CancelDelayedEvent();
15346     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15347   }
15348   cps->initDone = val;
15349 }
15350
15351 /* Parse feature command from engine */
15352 void
15353 ParseFeatures (char *args, ChessProgramState *cps)
15354 {
15355   char *p = args;
15356   char *q;
15357   int val;
15358   char buf[MSG_SIZ];
15359
15360   for (;;) {
15361     while (*p == ' ') p++;
15362     if (*p == NULLCHAR) return;
15363
15364     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15365     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15366     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15367     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15368     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15369     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15370     if (BoolFeature(&p, "reuse", &val, cps)) {
15371       /* Engine can disable reuse, but can't enable it if user said no */
15372       if (!val) cps->reuse = FALSE;
15373       continue;
15374     }
15375     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15376     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15377       if (gameMode == TwoMachinesPlay) {
15378         DisplayTwoMachinesTitle();
15379       } else {
15380         DisplayTitle("");
15381       }
15382       continue;
15383     }
15384     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15385     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15386     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15387     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15388     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15389     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15390     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15391     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15392     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15393     if (IntFeature(&p, "done", &val, cps)) {
15394       FeatureDone(cps, val);
15395       continue;
15396     }
15397     /* Added by Tord: */
15398     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15399     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15400     /* End of additions by Tord */
15401
15402     /* [HGM] added features: */
15403     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15404     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15405     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15406     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15407     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15408     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15409     if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15410         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15411           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15412             SendToProgram(buf, cps);
15413             continue;
15414         }
15415         if(cps->nrOptions >= MAX_OPTIONS) {
15416             cps->nrOptions--;
15417             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15418             DisplayError(buf, 0);
15419         }
15420         continue;
15421     }
15422     /* End of additions by HGM */
15423
15424     /* unknown feature: complain and skip */
15425     q = p;
15426     while (*q && *q != '=') q++;
15427     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15428     SendToProgram(buf, cps);
15429     p = q;
15430     if (*p == '=') {
15431       p++;
15432       if (*p == '\"') {
15433         p++;
15434         while (*p && *p != '\"') p++;
15435         if (*p == '\"') p++;
15436       } else {
15437         while (*p && *p != ' ') p++;
15438       }
15439     }
15440   }
15441
15442 }
15443
15444 void
15445 PeriodicUpdatesEvent (int newState)
15446 {
15447     if (newState == appData.periodicUpdates)
15448       return;
15449
15450     appData.periodicUpdates=newState;
15451
15452     /* Display type changes, so update it now */
15453 //    DisplayAnalysis();
15454
15455     /* Get the ball rolling again... */
15456     if (newState) {
15457         AnalysisPeriodicEvent(1);
15458         StartAnalysisClock();
15459     }
15460 }
15461
15462 void
15463 PonderNextMoveEvent (int newState)
15464 {
15465     if (newState == appData.ponderNextMove) return;
15466     if (gameMode == EditPosition) EditPositionDone(TRUE);
15467     if (newState) {
15468         SendToProgram("hard\n", &first);
15469         if (gameMode == TwoMachinesPlay) {
15470             SendToProgram("hard\n", &second);
15471         }
15472     } else {
15473         SendToProgram("easy\n", &first);
15474         thinkOutput[0] = NULLCHAR;
15475         if (gameMode == TwoMachinesPlay) {
15476             SendToProgram("easy\n", &second);
15477         }
15478     }
15479     appData.ponderNextMove = newState;
15480 }
15481
15482 void
15483 NewSettingEvent (int option, int *feature, char *command, int value)
15484 {
15485     char buf[MSG_SIZ];
15486
15487     if (gameMode == EditPosition) EditPositionDone(TRUE);
15488     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15489     if(feature == NULL || *feature) SendToProgram(buf, &first);
15490     if (gameMode == TwoMachinesPlay) {
15491         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15492     }
15493 }
15494
15495 void
15496 ShowThinkingEvent ()
15497 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15498 {
15499     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15500     int newState = appData.showThinking
15501         // [HGM] thinking: other features now need thinking output as well
15502         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15503
15504     if (oldState == newState) return;
15505     oldState = newState;
15506     if (gameMode == EditPosition) EditPositionDone(TRUE);
15507     if (oldState) {
15508         SendToProgram("post\n", &first);
15509         if (gameMode == TwoMachinesPlay) {
15510             SendToProgram("post\n", &second);
15511         }
15512     } else {
15513         SendToProgram("nopost\n", &first);
15514         thinkOutput[0] = NULLCHAR;
15515         if (gameMode == TwoMachinesPlay) {
15516             SendToProgram("nopost\n", &second);
15517         }
15518     }
15519 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15520 }
15521
15522 void
15523 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15524 {
15525   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15526   if (pr == NoProc) return;
15527   AskQuestion(title, question, replyPrefix, pr);
15528 }
15529
15530 void
15531 TypeInEvent (char firstChar)
15532 {
15533     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15534         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15535         gameMode == AnalyzeMode || gameMode == EditGame || 
15536         gameMode == EditPosition || gameMode == IcsExamining ||
15537         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15538         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15539                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15540                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15541         gameMode == Training) PopUpMoveDialog(firstChar);
15542 }
15543
15544 void
15545 TypeInDoneEvent (char *move)
15546 {
15547         Board board;
15548         int n, fromX, fromY, toX, toY;
15549         char promoChar;
15550         ChessMove moveType;
15551
15552         // [HGM] FENedit
15553         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15554                 EditPositionPasteFEN(move);
15555                 return;
15556         }
15557         // [HGM] movenum: allow move number to be typed in any mode
15558         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15559           ToNrEvent(2*n-1);
15560           return;
15561         }
15562
15563       if (gameMode != EditGame && currentMove != forwardMostMove && 
15564         gameMode != Training) {
15565         DisplayMoveError(_("Displayed move is not current"));
15566       } else {
15567         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15568           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15569         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15570         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15571           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15572           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15573         } else {
15574           DisplayMoveError(_("Could not parse move"));
15575         }
15576       }
15577 }
15578
15579 void
15580 DisplayMove (int moveNumber)
15581 {
15582     char message[MSG_SIZ];
15583     char res[MSG_SIZ];
15584     char cpThinkOutput[MSG_SIZ];
15585
15586     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15587
15588     if (moveNumber == forwardMostMove - 1 ||
15589         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15590
15591         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15592
15593         if (strchr(cpThinkOutput, '\n')) {
15594             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15595         }
15596     } else {
15597         *cpThinkOutput = NULLCHAR;
15598     }
15599
15600     /* [AS] Hide thinking from human user */
15601     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15602         *cpThinkOutput = NULLCHAR;
15603         if( thinkOutput[0] != NULLCHAR ) {
15604             int i;
15605
15606             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15607                 cpThinkOutput[i] = '.';
15608             }
15609             cpThinkOutput[i] = NULLCHAR;
15610             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15611         }
15612     }
15613
15614     if (moveNumber == forwardMostMove - 1 &&
15615         gameInfo.resultDetails != NULL) {
15616         if (gameInfo.resultDetails[0] == NULLCHAR) {
15617           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15618         } else {
15619           snprintf(res, MSG_SIZ, " {%s} %s",
15620                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15621         }
15622     } else {
15623         res[0] = NULLCHAR;
15624     }
15625
15626     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15627         DisplayMessage(res, cpThinkOutput);
15628     } else {
15629       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15630                 WhiteOnMove(moveNumber) ? " " : ".. ",
15631                 parseList[moveNumber], res);
15632         DisplayMessage(message, cpThinkOutput);
15633     }
15634 }
15635
15636 void
15637 DisplayComment (int moveNumber, char *text)
15638 {
15639     char title[MSG_SIZ];
15640
15641     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15642       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15643     } else {
15644       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15645               WhiteOnMove(moveNumber) ? " " : ".. ",
15646               parseList[moveNumber]);
15647     }
15648     if (text != NULL && (appData.autoDisplayComment || commentUp))
15649         CommentPopUp(title, text);
15650 }
15651
15652 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15653  * might be busy thinking or pondering.  It can be omitted if your
15654  * gnuchess is configured to stop thinking immediately on any user
15655  * input.  However, that gnuchess feature depends on the FIONREAD
15656  * ioctl, which does not work properly on some flavors of Unix.
15657  */
15658 void
15659 Attention (ChessProgramState *cps)
15660 {
15661 #if ATTENTION
15662     if (!cps->useSigint) return;
15663     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15664     switch (gameMode) {
15665       case MachinePlaysWhite:
15666       case MachinePlaysBlack:
15667       case TwoMachinesPlay:
15668       case IcsPlayingWhite:
15669       case IcsPlayingBlack:
15670       case AnalyzeMode:
15671       case AnalyzeFile:
15672         /* Skip if we know it isn't thinking */
15673         if (!cps->maybeThinking) return;
15674         if (appData.debugMode)
15675           fprintf(debugFP, "Interrupting %s\n", cps->which);
15676         InterruptChildProcess(cps->pr);
15677         cps->maybeThinking = FALSE;
15678         break;
15679       default:
15680         break;
15681     }
15682 #endif /*ATTENTION*/
15683 }
15684
15685 int
15686 CheckFlags ()
15687 {
15688     if (whiteTimeRemaining <= 0) {
15689         if (!whiteFlag) {
15690             whiteFlag = TRUE;
15691             if (appData.icsActive) {
15692                 if (appData.autoCallFlag &&
15693                     gameMode == IcsPlayingBlack && !blackFlag) {
15694                   SendToICS(ics_prefix);
15695                   SendToICS("flag\n");
15696                 }
15697             } else {
15698                 if (blackFlag) {
15699                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15700                 } else {
15701                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15702                     if (appData.autoCallFlag) {
15703                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15704                         return TRUE;
15705                     }
15706                 }
15707             }
15708         }
15709     }
15710     if (blackTimeRemaining <= 0) {
15711         if (!blackFlag) {
15712             blackFlag = TRUE;
15713             if (appData.icsActive) {
15714                 if (appData.autoCallFlag &&
15715                     gameMode == IcsPlayingWhite && !whiteFlag) {
15716                   SendToICS(ics_prefix);
15717                   SendToICS("flag\n");
15718                 }
15719             } else {
15720                 if (whiteFlag) {
15721                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15722                 } else {
15723                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15724                     if (appData.autoCallFlag) {
15725                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15726                         return TRUE;
15727                     }
15728                 }
15729             }
15730         }
15731     }
15732     return FALSE;
15733 }
15734
15735 void
15736 CheckTimeControl ()
15737 {
15738     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15739         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15740
15741     /*
15742      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15743      */
15744     if ( !WhiteOnMove(forwardMostMove) ) {
15745         /* White made time control */
15746         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15747         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15748         /* [HGM] time odds: correct new time quota for time odds! */
15749                                             / WhitePlayer()->timeOdds;
15750         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15751     } else {
15752         lastBlack -= blackTimeRemaining;
15753         /* Black made time control */
15754         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15755                                             / WhitePlayer()->other->timeOdds;
15756         lastWhite = whiteTimeRemaining;
15757     }
15758 }
15759
15760 void
15761 DisplayBothClocks ()
15762 {
15763     int wom = gameMode == EditPosition ?
15764       !blackPlaysFirst : WhiteOnMove(currentMove);
15765     DisplayWhiteClock(whiteTimeRemaining, wom);
15766     DisplayBlackClock(blackTimeRemaining, !wom);
15767 }
15768
15769
15770 /* Timekeeping seems to be a portability nightmare.  I think everyone
15771    has ftime(), but I'm really not sure, so I'm including some ifdefs
15772    to use other calls if you don't.  Clocks will be less accurate if
15773    you have neither ftime nor gettimeofday.
15774 */
15775
15776 /* VS 2008 requires the #include outside of the function */
15777 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15778 #include <sys/timeb.h>
15779 #endif
15780
15781 /* Get the current time as a TimeMark */
15782 void
15783 GetTimeMark (TimeMark *tm)
15784 {
15785 #if HAVE_GETTIMEOFDAY
15786
15787     struct timeval timeVal;
15788     struct timezone timeZone;
15789
15790     gettimeofday(&timeVal, &timeZone);
15791     tm->sec = (long) timeVal.tv_sec;
15792     tm->ms = (int) (timeVal.tv_usec / 1000L);
15793
15794 #else /*!HAVE_GETTIMEOFDAY*/
15795 #if HAVE_FTIME
15796
15797 // include <sys/timeb.h> / moved to just above start of function
15798     struct timeb timeB;
15799
15800     ftime(&timeB);
15801     tm->sec = (long) timeB.time;
15802     tm->ms = (int) timeB.millitm;
15803
15804 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15805     tm->sec = (long) time(NULL);
15806     tm->ms = 0;
15807 #endif
15808 #endif
15809 }
15810
15811 /* Return the difference in milliseconds between two
15812    time marks.  We assume the difference will fit in a long!
15813 */
15814 long
15815 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
15816 {
15817     return 1000L*(tm2->sec - tm1->sec) +
15818            (long) (tm2->ms - tm1->ms);
15819 }
15820
15821
15822 /*
15823  * Code to manage the game clocks.
15824  *
15825  * In tournament play, black starts the clock and then white makes a move.
15826  * We give the human user a slight advantage if he is playing white---the
15827  * clocks don't run until he makes his first move, so it takes zero time.
15828  * Also, we don't account for network lag, so we could get out of sync
15829  * with GNU Chess's clock -- but then, referees are always right.
15830  */
15831
15832 static TimeMark tickStartTM;
15833 static long intendedTickLength;
15834
15835 long
15836 NextTickLength (long timeRemaining)
15837 {
15838     long nominalTickLength, nextTickLength;
15839
15840     if (timeRemaining > 0L && timeRemaining <= 10000L)
15841       nominalTickLength = 100L;
15842     else
15843       nominalTickLength = 1000L;
15844     nextTickLength = timeRemaining % nominalTickLength;
15845     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15846
15847     return nextTickLength;
15848 }
15849
15850 /* Adjust clock one minute up or down */
15851 void
15852 AdjustClock (Boolean which, int dir)
15853 {
15854     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15855     if(which) blackTimeRemaining += 60000*dir;
15856     else      whiteTimeRemaining += 60000*dir;
15857     DisplayBothClocks();
15858     adjustedClock = TRUE;
15859 }
15860
15861 /* Stop clocks and reset to a fresh time control */
15862 void
15863 ResetClocks ()
15864 {
15865     (void) StopClockTimer();
15866     if (appData.icsActive) {
15867         whiteTimeRemaining = blackTimeRemaining = 0;
15868     } else if (searchTime) {
15869         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15870         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15871     } else { /* [HGM] correct new time quote for time odds */
15872         whiteTC = blackTC = fullTimeControlString;
15873         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15874         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15875     }
15876     if (whiteFlag || blackFlag) {
15877         DisplayTitle("");
15878         whiteFlag = blackFlag = FALSE;
15879     }
15880     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15881     DisplayBothClocks();
15882     adjustedClock = FALSE;
15883 }
15884
15885 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15886
15887 /* Decrement running clock by amount of time that has passed */
15888 void
15889 DecrementClocks ()
15890 {
15891     long timeRemaining;
15892     long lastTickLength, fudge;
15893     TimeMark now;
15894
15895     if (!appData.clockMode) return;
15896     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15897
15898     GetTimeMark(&now);
15899
15900     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15901
15902     /* Fudge if we woke up a little too soon */
15903     fudge = intendedTickLength - lastTickLength;
15904     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15905
15906     if (WhiteOnMove(forwardMostMove)) {
15907         if(whiteNPS >= 0) lastTickLength = 0;
15908         timeRemaining = whiteTimeRemaining -= lastTickLength;
15909         if(timeRemaining < 0 && !appData.icsActive) {
15910             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15911             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15912                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15913                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15914             }
15915         }
15916         DisplayWhiteClock(whiteTimeRemaining - fudge,
15917                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15918     } else {
15919         if(blackNPS >= 0) lastTickLength = 0;
15920         timeRemaining = blackTimeRemaining -= lastTickLength;
15921         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15922             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15923             if(suddenDeath) {
15924                 blackStartMove = forwardMostMove;
15925                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15926             }
15927         }
15928         DisplayBlackClock(blackTimeRemaining - fudge,
15929                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15930     }
15931     if (CheckFlags()) return;
15932
15933     tickStartTM = now;
15934     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15935     StartClockTimer(intendedTickLength);
15936
15937     /* if the time remaining has fallen below the alarm threshold, sound the
15938      * alarm. if the alarm has sounded and (due to a takeback or time control
15939      * with increment) the time remaining has increased to a level above the
15940      * threshold, reset the alarm so it can sound again.
15941      */
15942
15943     if (appData.icsActive && appData.icsAlarm) {
15944
15945         /* make sure we are dealing with the user's clock */
15946         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15947                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15948            )) return;
15949
15950         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15951             alarmSounded = FALSE;
15952         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15953             PlayAlarmSound();
15954             alarmSounded = TRUE;
15955         }
15956     }
15957 }
15958
15959
15960 /* A player has just moved, so stop the previously running
15961    clock and (if in clock mode) start the other one.
15962    We redisplay both clocks in case we're in ICS mode, because
15963    ICS gives us an update to both clocks after every move.
15964    Note that this routine is called *after* forwardMostMove
15965    is updated, so the last fractional tick must be subtracted
15966    from the color that is *not* on move now.
15967 */
15968 void
15969 SwitchClocks (int newMoveNr)
15970 {
15971     long lastTickLength;
15972     TimeMark now;
15973     int flagged = FALSE;
15974
15975     GetTimeMark(&now);
15976
15977     if (StopClockTimer() && appData.clockMode) {
15978         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15979         if (!WhiteOnMove(forwardMostMove)) {
15980             if(blackNPS >= 0) lastTickLength = 0;
15981             blackTimeRemaining -= lastTickLength;
15982            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15983 //         if(pvInfoList[forwardMostMove].time == -1)
15984                  pvInfoList[forwardMostMove].time =               // use GUI time
15985                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15986         } else {
15987            if(whiteNPS >= 0) lastTickLength = 0;
15988            whiteTimeRemaining -= lastTickLength;
15989            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15990 //         if(pvInfoList[forwardMostMove].time == -1)
15991                  pvInfoList[forwardMostMove].time =
15992                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15993         }
15994         flagged = CheckFlags();
15995     }
15996     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15997     CheckTimeControl();
15998
15999     if (flagged || !appData.clockMode) return;
16000
16001     switch (gameMode) {
16002       case MachinePlaysBlack:
16003       case MachinePlaysWhite:
16004       case BeginningOfGame:
16005         if (pausing) return;
16006         break;
16007
16008       case EditGame:
16009       case PlayFromGameFile:
16010       case IcsExamining:
16011         return;
16012
16013       default:
16014         break;
16015     }
16016
16017     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16018         if(WhiteOnMove(forwardMostMove))
16019              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16020         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16021     }
16022
16023     tickStartTM = now;
16024     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16025       whiteTimeRemaining : blackTimeRemaining);
16026     StartClockTimer(intendedTickLength);
16027 }
16028
16029
16030 /* Stop both clocks */
16031 void
16032 StopClocks ()
16033 {
16034     long lastTickLength;
16035     TimeMark now;
16036
16037     if (!StopClockTimer()) return;
16038     if (!appData.clockMode) return;
16039
16040     GetTimeMark(&now);
16041
16042     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16043     if (WhiteOnMove(forwardMostMove)) {
16044         if(whiteNPS >= 0) lastTickLength = 0;
16045         whiteTimeRemaining -= lastTickLength;
16046         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16047     } else {
16048         if(blackNPS >= 0) lastTickLength = 0;
16049         blackTimeRemaining -= lastTickLength;
16050         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16051     }
16052     CheckFlags();
16053 }
16054
16055 /* Start clock of player on move.  Time may have been reset, so
16056    if clock is already running, stop and restart it. */
16057 void
16058 StartClocks ()
16059 {
16060     (void) StopClockTimer(); /* in case it was running already */
16061     DisplayBothClocks();
16062     if (CheckFlags()) return;
16063
16064     if (!appData.clockMode) return;
16065     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16066
16067     GetTimeMark(&tickStartTM);
16068     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16069       whiteTimeRemaining : blackTimeRemaining);
16070
16071    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16072     whiteNPS = blackNPS = -1;
16073     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16074        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16075         whiteNPS = first.nps;
16076     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16077        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16078         blackNPS = first.nps;
16079     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16080         whiteNPS = second.nps;
16081     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16082         blackNPS = second.nps;
16083     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16084
16085     StartClockTimer(intendedTickLength);
16086 }
16087
16088 char *
16089 TimeString (long ms)
16090 {
16091     long second, minute, hour, day;
16092     char *sign = "";
16093     static char buf[32];
16094
16095     if (ms > 0 && ms <= 9900) {
16096       /* convert milliseconds to tenths, rounding up */
16097       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16098
16099       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16100       return buf;
16101     }
16102
16103     /* convert milliseconds to seconds, rounding up */
16104     /* use floating point to avoid strangeness of integer division
16105        with negative dividends on many machines */
16106     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16107
16108     if (second < 0) {
16109         sign = "-";
16110         second = -second;
16111     }
16112
16113     day = second / (60 * 60 * 24);
16114     second = second % (60 * 60 * 24);
16115     hour = second / (60 * 60);
16116     second = second % (60 * 60);
16117     minute = second / 60;
16118     second = second % 60;
16119
16120     if (day > 0)
16121       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16122               sign, day, hour, minute, second);
16123     else if (hour > 0)
16124       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16125     else
16126       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16127
16128     return buf;
16129 }
16130
16131
16132 /*
16133  * This is necessary because some C libraries aren't ANSI C compliant yet.
16134  */
16135 char *
16136 StrStr (char *string, char *match)
16137 {
16138     int i, length;
16139
16140     length = strlen(match);
16141
16142     for (i = strlen(string) - length; i >= 0; i--, string++)
16143       if (!strncmp(match, string, length))
16144         return string;
16145
16146     return NULL;
16147 }
16148
16149 char *
16150 StrCaseStr (char *string, char *match)
16151 {
16152     int i, j, length;
16153
16154     length = strlen(match);
16155
16156     for (i = strlen(string) - length; i >= 0; i--, string++) {
16157         for (j = 0; j < length; j++) {
16158             if (ToLower(match[j]) != ToLower(string[j]))
16159               break;
16160         }
16161         if (j == length) return string;
16162     }
16163
16164     return NULL;
16165 }
16166
16167 #ifndef _amigados
16168 int
16169 StrCaseCmp (char *s1, char *s2)
16170 {
16171     char c1, c2;
16172
16173     for (;;) {
16174         c1 = ToLower(*s1++);
16175         c2 = ToLower(*s2++);
16176         if (c1 > c2) return 1;
16177         if (c1 < c2) return -1;
16178         if (c1 == NULLCHAR) return 0;
16179     }
16180 }
16181
16182
16183 int
16184 ToLower (int c)
16185 {
16186     return isupper(c) ? tolower(c) : c;
16187 }
16188
16189
16190 int
16191 ToUpper (int c)
16192 {
16193     return islower(c) ? toupper(c) : c;
16194 }
16195 #endif /* !_amigados    */
16196
16197 char *
16198 StrSave (char *s)
16199 {
16200   char *ret;
16201
16202   if ((ret = (char *) malloc(strlen(s) + 1)))
16203     {
16204       safeStrCpy(ret, s, strlen(s)+1);
16205     }
16206   return ret;
16207 }
16208
16209 char *
16210 StrSavePtr (char *s, char **savePtr)
16211 {
16212     if (*savePtr) {
16213         free(*savePtr);
16214     }
16215     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16216       safeStrCpy(*savePtr, s, strlen(s)+1);
16217     }
16218     return(*savePtr);
16219 }
16220
16221 char *
16222 PGNDate ()
16223 {
16224     time_t clock;
16225     struct tm *tm;
16226     char buf[MSG_SIZ];
16227
16228     clock = time((time_t *)NULL);
16229     tm = localtime(&clock);
16230     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16231             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16232     return StrSave(buf);
16233 }
16234
16235
16236 char *
16237 PositionToFEN (int move, char *overrideCastling)
16238 {
16239     int i, j, fromX, fromY, toX, toY;
16240     int whiteToPlay;
16241     char buf[MSG_SIZ];
16242     char *p, *q;
16243     int emptycount;
16244     ChessSquare piece;
16245
16246     whiteToPlay = (gameMode == EditPosition) ?
16247       !blackPlaysFirst : (move % 2 == 0);
16248     p = buf;
16249
16250     /* Piece placement data */
16251     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16252         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16253         emptycount = 0;
16254         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16255             if (boards[move][i][j] == EmptySquare) {
16256                 emptycount++;
16257             } else { ChessSquare piece = boards[move][i][j];
16258                 if (emptycount > 0) {
16259                     if(emptycount<10) /* [HGM] can be >= 10 */
16260                         *p++ = '0' + emptycount;
16261                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16262                     emptycount = 0;
16263                 }
16264                 if(PieceToChar(piece) == '+') {
16265                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16266                     *p++ = '+';
16267                     piece = (ChessSquare)(DEMOTED piece);
16268                 }
16269                 *p++ = PieceToChar(piece);
16270                 if(p[-1] == '~') {
16271                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16272                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16273                     *p++ = '~';
16274                 }
16275             }
16276         }
16277         if (emptycount > 0) {
16278             if(emptycount<10) /* [HGM] can be >= 10 */
16279                 *p++ = '0' + emptycount;
16280             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16281             emptycount = 0;
16282         }
16283         *p++ = '/';
16284     }
16285     *(p - 1) = ' ';
16286
16287     /* [HGM] print Crazyhouse or Shogi holdings */
16288     if( gameInfo.holdingsWidth ) {
16289         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16290         q = p;
16291         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16292             piece = boards[move][i][BOARD_WIDTH-1];
16293             if( piece != EmptySquare )
16294               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16295                   *p++ = PieceToChar(piece);
16296         }
16297         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16298             piece = boards[move][BOARD_HEIGHT-i-1][0];
16299             if( piece != EmptySquare )
16300               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16301                   *p++ = PieceToChar(piece);
16302         }
16303
16304         if( q == p ) *p++ = '-';
16305         *p++ = ']';
16306         *p++ = ' ';
16307     }
16308
16309     /* Active color */
16310     *p++ = whiteToPlay ? 'w' : 'b';
16311     *p++ = ' ';
16312
16313   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16314     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16315   } else {
16316   if(nrCastlingRights) {
16317      q = p;
16318      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16319        /* [HGM] write directly from rights */
16320            if(boards[move][CASTLING][2] != NoRights &&
16321               boards[move][CASTLING][0] != NoRights   )
16322                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16323            if(boards[move][CASTLING][2] != NoRights &&
16324               boards[move][CASTLING][1] != NoRights   )
16325                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16326            if(boards[move][CASTLING][5] != NoRights &&
16327               boards[move][CASTLING][3] != NoRights   )
16328                 *p++ = boards[move][CASTLING][3] + AAA;
16329            if(boards[move][CASTLING][5] != NoRights &&
16330               boards[move][CASTLING][4] != NoRights   )
16331                 *p++ = boards[move][CASTLING][4] + AAA;
16332      } else {
16333
16334         /* [HGM] write true castling rights */
16335         if( nrCastlingRights == 6 ) {
16336             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16337                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16338             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16339                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16340             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16341                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16342             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16343                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16344         }
16345      }
16346      if (q == p) *p++ = '-'; /* No castling rights */
16347      *p++ = ' ';
16348   }
16349
16350   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16351      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16352     /* En passant target square */
16353     if (move > backwardMostMove) {
16354         fromX = moveList[move - 1][0] - AAA;
16355         fromY = moveList[move - 1][1] - ONE;
16356         toX = moveList[move - 1][2] - AAA;
16357         toY = moveList[move - 1][3] - ONE;
16358         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16359             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16360             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16361             fromX == toX) {
16362             /* 2-square pawn move just happened */
16363             *p++ = toX + AAA;
16364             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16365         } else {
16366             *p++ = '-';
16367         }
16368     } else if(move == backwardMostMove) {
16369         // [HGM] perhaps we should always do it like this, and forget the above?
16370         if((signed char)boards[move][EP_STATUS] >= 0) {
16371             *p++ = boards[move][EP_STATUS] + AAA;
16372             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16373         } else {
16374             *p++ = '-';
16375         }
16376     } else {
16377         *p++ = '-';
16378     }
16379     *p++ = ' ';
16380   }
16381   }
16382
16383     /* [HGM] find reversible plies */
16384     {   int i = 0, j=move;
16385
16386         if (appData.debugMode) { int k;
16387             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16388             for(k=backwardMostMove; k<=forwardMostMove; k++)
16389                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16390
16391         }
16392
16393         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16394         if( j == backwardMostMove ) i += initialRulePlies;
16395         sprintf(p, "%d ", i);
16396         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16397     }
16398     /* Fullmove number */
16399     sprintf(p, "%d", (move / 2) + 1);
16400
16401     return StrSave(buf);
16402 }
16403
16404 Boolean
16405 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16406 {
16407     int i, j;
16408     char *p, c;
16409     int emptycount;
16410     ChessSquare piece;
16411
16412     p = fen;
16413
16414     /* [HGM] by default clear Crazyhouse holdings, if present */
16415     if(gameInfo.holdingsWidth) {
16416        for(i=0; i<BOARD_HEIGHT; i++) {
16417            board[i][0]             = EmptySquare; /* black holdings */
16418            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16419            board[i][1]             = (ChessSquare) 0; /* black counts */
16420            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16421        }
16422     }
16423
16424     /* Piece placement data */
16425     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16426         j = 0;
16427         for (;;) {
16428             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16429                 if (*p == '/') p++;
16430                 emptycount = gameInfo.boardWidth - j;
16431                 while (emptycount--)
16432                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16433                 break;
16434 #if(BOARD_FILES >= 10)
16435             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16436                 p++; emptycount=10;
16437                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16438                 while (emptycount--)
16439                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16440 #endif
16441             } else if (isdigit(*p)) {
16442                 emptycount = *p++ - '0';
16443                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16444                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16445                 while (emptycount--)
16446                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16447             } else if (*p == '+' || isalpha(*p)) {
16448                 if (j >= gameInfo.boardWidth) return FALSE;
16449                 if(*p=='+') {
16450                     piece = CharToPiece(*++p);
16451                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16452                     piece = (ChessSquare) (PROMOTED piece ); p++;
16453                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16454                 } else piece = CharToPiece(*p++);
16455
16456                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16457                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16458                     piece = (ChessSquare) (PROMOTED piece);
16459                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16460                     p++;
16461                 }
16462                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16463             } else {
16464                 return FALSE;
16465             }
16466         }
16467     }
16468     while (*p == '/' || *p == ' ') p++;
16469
16470     /* [HGM] look for Crazyhouse holdings here */
16471     while(*p==' ') p++;
16472     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16473         if(*p == '[') p++;
16474         if(*p == '-' ) p++; /* empty holdings */ else {
16475             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16476             /* if we would allow FEN reading to set board size, we would   */
16477             /* have to add holdings and shift the board read so far here   */
16478             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16479                 p++;
16480                 if((int) piece >= (int) BlackPawn ) {
16481                     i = (int)piece - (int)BlackPawn;
16482                     i = PieceToNumber((ChessSquare)i);
16483                     if( i >= gameInfo.holdingsSize ) return FALSE;
16484                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16485                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16486                 } else {
16487                     i = (int)piece - (int)WhitePawn;
16488                     i = PieceToNumber((ChessSquare)i);
16489                     if( i >= gameInfo.holdingsSize ) return FALSE;
16490                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16491                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16492                 }
16493             }
16494         }
16495         if(*p == ']') p++;
16496     }
16497
16498     while(*p == ' ') p++;
16499
16500     /* Active color */
16501     c = *p++;
16502     if(appData.colorNickNames) {
16503       if( c == appData.colorNickNames[0] ) c = 'w'; else
16504       if( c == appData.colorNickNames[1] ) c = 'b';
16505     }
16506     switch (c) {
16507       case 'w':
16508         *blackPlaysFirst = FALSE;
16509         break;
16510       case 'b':
16511         *blackPlaysFirst = TRUE;
16512         break;
16513       default:
16514         return FALSE;
16515     }
16516
16517     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16518     /* return the extra info in global variiables             */
16519
16520     /* set defaults in case FEN is incomplete */
16521     board[EP_STATUS] = EP_UNKNOWN;
16522     for(i=0; i<nrCastlingRights; i++ ) {
16523         board[CASTLING][i] =
16524             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16525     }   /* assume possible unless obviously impossible */
16526     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16527     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16528     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16529                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16530     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16531     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16532     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16533                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16534     FENrulePlies = 0;
16535
16536     while(*p==' ') p++;
16537     if(nrCastlingRights) {
16538       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16539           /* castling indicator present, so default becomes no castlings */
16540           for(i=0; i<nrCastlingRights; i++ ) {
16541                  board[CASTLING][i] = NoRights;
16542           }
16543       }
16544       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16545              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16546              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16547              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16548         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16549
16550         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16551             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16552             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16553         }
16554         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16555             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16556         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16557                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16558         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16559                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16560         switch(c) {
16561           case'K':
16562               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16563               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16564               board[CASTLING][2] = whiteKingFile;
16565               break;
16566           case'Q':
16567               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16568               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16569               board[CASTLING][2] = whiteKingFile;
16570               break;
16571           case'k':
16572               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16573               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16574               board[CASTLING][5] = blackKingFile;
16575               break;
16576           case'q':
16577               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16578               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16579               board[CASTLING][5] = blackKingFile;
16580           case '-':
16581               break;
16582           default: /* FRC castlings */
16583               if(c >= 'a') { /* black rights */
16584                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16585                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16586                   if(i == BOARD_RGHT) break;
16587                   board[CASTLING][5] = i;
16588                   c -= AAA;
16589                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16590                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16591                   if(c > i)
16592                       board[CASTLING][3] = c;
16593                   else
16594                       board[CASTLING][4] = c;
16595               } else { /* white rights */
16596                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16597                     if(board[0][i] == WhiteKing) break;
16598                   if(i == BOARD_RGHT) break;
16599                   board[CASTLING][2] = i;
16600                   c -= AAA - 'a' + 'A';
16601                   if(board[0][c] >= WhiteKing) break;
16602                   if(c > i)
16603                       board[CASTLING][0] = c;
16604                   else
16605                       board[CASTLING][1] = c;
16606               }
16607         }
16608       }
16609       for(i=0; i<nrCastlingRights; i++)
16610         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16611     if (appData.debugMode) {
16612         fprintf(debugFP, "FEN castling rights:");
16613         for(i=0; i<nrCastlingRights; i++)
16614         fprintf(debugFP, " %d", board[CASTLING][i]);
16615         fprintf(debugFP, "\n");
16616     }
16617
16618       while(*p==' ') p++;
16619     }
16620
16621     /* read e.p. field in games that know e.p. capture */
16622     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16623        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16624       if(*p=='-') {
16625         p++; board[EP_STATUS] = EP_NONE;
16626       } else {
16627          char c = *p++ - AAA;
16628
16629          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16630          if(*p >= '0' && *p <='9') p++;
16631          board[EP_STATUS] = c;
16632       }
16633     }
16634
16635
16636     if(sscanf(p, "%d", &i) == 1) {
16637         FENrulePlies = i; /* 50-move ply counter */
16638         /* (The move number is still ignored)    */
16639     }
16640
16641     return TRUE;
16642 }
16643
16644 void
16645 EditPositionPasteFEN (char *fen)
16646 {
16647   if (fen != NULL) {
16648     Board initial_position;
16649
16650     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16651       DisplayError(_("Bad FEN position in clipboard"), 0);
16652       return ;
16653     } else {
16654       int savedBlackPlaysFirst = blackPlaysFirst;
16655       EditPositionEvent();
16656       blackPlaysFirst = savedBlackPlaysFirst;
16657       CopyBoard(boards[0], initial_position);
16658       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16659       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16660       DisplayBothClocks();
16661       DrawPosition(FALSE, boards[currentMove]);
16662     }
16663   }
16664 }
16665
16666 static char cseq[12] = "\\   ";
16667
16668 Boolean
16669 set_cont_sequence (char *new_seq)
16670 {
16671     int len;
16672     Boolean ret;
16673
16674     // handle bad attempts to set the sequence
16675         if (!new_seq)
16676                 return 0; // acceptable error - no debug
16677
16678     len = strlen(new_seq);
16679     ret = (len > 0) && (len < sizeof(cseq));
16680     if (ret)
16681       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16682     else if (appData.debugMode)
16683       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16684     return ret;
16685 }
16686
16687 /*
16688     reformat a source message so words don't cross the width boundary.  internal
16689     newlines are not removed.  returns the wrapped size (no null character unless
16690     included in source message).  If dest is NULL, only calculate the size required
16691     for the dest buffer.  lp argument indicats line position upon entry, and it's
16692     passed back upon exit.
16693 */
16694 int
16695 wrap (char *dest, char *src, int count, int width, int *lp)
16696 {
16697     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16698
16699     cseq_len = strlen(cseq);
16700     old_line = line = *lp;
16701     ansi = len = clen = 0;
16702
16703     for (i=0; i < count; i++)
16704     {
16705         if (src[i] == '\033')
16706             ansi = 1;
16707
16708         // if we hit the width, back up
16709         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16710         {
16711             // store i & len in case the word is too long
16712             old_i = i, old_len = len;
16713
16714             // find the end of the last word
16715             while (i && src[i] != ' ' && src[i] != '\n')
16716             {
16717                 i--;
16718                 len--;
16719             }
16720
16721             // word too long?  restore i & len before splitting it
16722             if ((old_i-i+clen) >= width)
16723             {
16724                 i = old_i;
16725                 len = old_len;
16726             }
16727
16728             // extra space?
16729             if (i && src[i-1] == ' ')
16730                 len--;
16731
16732             if (src[i] != ' ' && src[i] != '\n')
16733             {
16734                 i--;
16735                 if (len)
16736                     len--;
16737             }
16738
16739             // now append the newline and continuation sequence
16740             if (dest)
16741                 dest[len] = '\n';
16742             len++;
16743             if (dest)
16744                 strncpy(dest+len, cseq, cseq_len);
16745             len += cseq_len;
16746             line = cseq_len;
16747             clen = cseq_len;
16748             continue;
16749         }
16750
16751         if (dest)
16752             dest[len] = src[i];
16753         len++;
16754         if (!ansi)
16755             line++;
16756         if (src[i] == '\n')
16757             line = 0;
16758         if (src[i] == 'm')
16759             ansi = 0;
16760     }
16761     if (dest && appData.debugMode)
16762     {
16763         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16764             count, width, line, len, *lp);
16765         show_bytes(debugFP, src, count);
16766         fprintf(debugFP, "\ndest: ");
16767         show_bytes(debugFP, dest, len);
16768         fprintf(debugFP, "\n");
16769     }
16770     *lp = dest ? line : old_line;
16771
16772     return len;
16773 }
16774
16775 // [HGM] vari: routines for shelving variations
16776 Boolean modeRestore = FALSE;
16777
16778 void
16779 PushInner (int firstMove, int lastMove)
16780 {
16781         int i, j, nrMoves = lastMove - firstMove;
16782
16783         // push current tail of game on stack
16784         savedResult[storedGames] = gameInfo.result;
16785         savedDetails[storedGames] = gameInfo.resultDetails;
16786         gameInfo.resultDetails = NULL;
16787         savedFirst[storedGames] = firstMove;
16788         savedLast [storedGames] = lastMove;
16789         savedFramePtr[storedGames] = framePtr;
16790         framePtr -= nrMoves; // reserve space for the boards
16791         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16792             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16793             for(j=0; j<MOVE_LEN; j++)
16794                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16795             for(j=0; j<2*MOVE_LEN; j++)
16796                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16797             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16798             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16799             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16800             pvInfoList[firstMove+i-1].depth = 0;
16801             commentList[framePtr+i] = commentList[firstMove+i];
16802             commentList[firstMove+i] = NULL;
16803         }
16804
16805         storedGames++;
16806         forwardMostMove = firstMove; // truncate game so we can start variation
16807 }
16808
16809 void
16810 PushTail (int firstMove, int lastMove)
16811 {
16812         if(appData.icsActive) { // only in local mode
16813                 forwardMostMove = currentMove; // mimic old ICS behavior
16814                 return;
16815         }
16816         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16817
16818         PushInner(firstMove, lastMove);
16819         if(storedGames == 1) GreyRevert(FALSE);
16820         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16821 }
16822
16823 void
16824 PopInner (Boolean annotate)
16825 {
16826         int i, j, nrMoves;
16827         char buf[8000], moveBuf[20];
16828
16829         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16830         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16831         nrMoves = savedLast[storedGames] - currentMove;
16832         if(annotate) {
16833                 int cnt = 10;
16834                 if(!WhiteOnMove(currentMove))
16835                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16836                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16837                 for(i=currentMove; i<forwardMostMove; i++) {
16838                         if(WhiteOnMove(i))
16839                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16840                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16841                         strcat(buf, moveBuf);
16842                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16843                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16844                 }
16845                 strcat(buf, ")");
16846         }
16847         for(i=1; i<=nrMoves; i++) { // copy last variation back
16848             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16849             for(j=0; j<MOVE_LEN; j++)
16850                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16851             for(j=0; j<2*MOVE_LEN; j++)
16852                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16853             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16854             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16855             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16856             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16857             commentList[currentMove+i] = commentList[framePtr+i];
16858             commentList[framePtr+i] = NULL;
16859         }
16860         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16861         framePtr = savedFramePtr[storedGames];
16862         gameInfo.result = savedResult[storedGames];
16863         if(gameInfo.resultDetails != NULL) {
16864             free(gameInfo.resultDetails);
16865       }
16866         gameInfo.resultDetails = savedDetails[storedGames];
16867         forwardMostMove = currentMove + nrMoves;
16868 }
16869
16870 Boolean
16871 PopTail (Boolean annotate)
16872 {
16873         if(appData.icsActive) return FALSE; // only in local mode
16874         if(!storedGames) return FALSE; // sanity
16875         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16876
16877         PopInner(annotate);
16878         if(currentMove < forwardMostMove) ForwardEvent(); else
16879         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16880
16881         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16882         return TRUE;
16883 }
16884
16885 void
16886 CleanupTail ()
16887 {       // remove all shelved variations
16888         int i;
16889         for(i=0; i<storedGames; i++) {
16890             if(savedDetails[i])
16891                 free(savedDetails[i]);
16892             savedDetails[i] = NULL;
16893         }
16894         for(i=framePtr; i<MAX_MOVES; i++) {
16895                 if(commentList[i]) free(commentList[i]);
16896                 commentList[i] = NULL;
16897         }
16898         framePtr = MAX_MOVES-1;
16899         storedGames = 0;
16900 }
16901
16902 void
16903 LoadVariation (int index, char *text)
16904 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16905         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16906         int level = 0, move;
16907
16908         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16909         // first find outermost bracketing variation
16910         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16911             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16912                 if(*p == '{') wait = '}'; else
16913                 if(*p == '[') wait = ']'; else
16914                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16915                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16916             }
16917             if(*p == wait) wait = NULLCHAR; // closing ]} found
16918             p++;
16919         }
16920         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16921         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16922         end[1] = NULLCHAR; // clip off comment beyond variation
16923         ToNrEvent(currentMove-1);
16924         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16925         // kludge: use ParsePV() to append variation to game
16926         move = currentMove;
16927         ParsePV(start, TRUE, TRUE);
16928         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16929         ClearPremoveHighlights();
16930         CommentPopDown();
16931         ToNrEvent(currentMove+1);
16932 }
16933