e6f48a450482513761d01c915681125f7efe1313
[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         appData.firstProtocolVersion = PROTOVER;
884         ParseArgsFromString(buf);
885         SwapEngines(i);
886         ReplaceEngine(cps, i);
887         return;
888     }
889     p = engineName;
890     while(q = strchr(p, SLASH)) p = q+1;
891     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
892     if(engineDir[0] != NULLCHAR)
893         appData.directory[i] = engineDir;
894     else if(p != engineName) { // derive directory from engine path, when not given
895         p[-1] = 0;
896         appData.directory[i] = strdup(engineName);
897         p[-1] = SLASH;
898     } else appData.directory[i] = ".";
899     if(params[0]) {
900         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
901         snprintf(command, MSG_SIZ, "%s %s", p, params);
902         p = command;
903     }
904     appData.chessProgram[i] = strdup(p);
905     appData.isUCI[i] = isUCI;
906     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
907     appData.hasOwnBookUCI[i] = hasBook;
908     if(!nickName[0]) useNick = FALSE;
909     if(useNick) ASSIGN(appData.pgnName[i], nickName);
910     if(addToList) {
911         int len;
912         char quote;
913         q = firstChessProgramNames;
914         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
915         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
916         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
917                         quote, p, quote, appData.directory[i], 
918                         useNick ? " -fn \"" : "",
919                         useNick ? nickName : "",
920                         useNick ? "\"" : "",
921                         v1 ? " -firstProtocolVersion 1" : "",
922                         hasBook ? "" : " -fNoOwnBookUCI",
923                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
924                         storeVariant ? " -variant " : "",
925                         storeVariant ? VariantName(gameInfo.variant) : "");
926         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
927         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
928         if(q)   free(q);
929     }
930     ReplaceEngine(cps, i);
931 }
932
933 void
934 InitTimeControls ()
935 {
936     int matched, min, sec;
937     /*
938      * Parse timeControl resource
939      */
940     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
941                           appData.movesPerSession)) {
942         char buf[MSG_SIZ];
943         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
944         DisplayFatalError(buf, 0, 2);
945     }
946
947     /*
948      * Parse searchTime resource
949      */
950     if (*appData.searchTime != NULLCHAR) {
951         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
952         if (matched == 1) {
953             searchTime = min * 60;
954         } else if (matched == 2) {
955             searchTime = min * 60 + sec;
956         } else {
957             char buf[MSG_SIZ];
958             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
959             DisplayFatalError(buf, 0, 2);
960         }
961     }
962 }
963
964 void
965 InitBackEnd1 ()
966 {
967
968     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
969     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
970
971     GetTimeMark(&programStartTime);
972     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
973     appData.seedBase = random() + (random()<<15);
974     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
975
976     ClearProgramStats();
977     programStats.ok_to_send = 1;
978     programStats.seen_stat = 0;
979
980     /*
981      * Initialize game list
982      */
983     ListNew(&gameList);
984
985
986     /*
987      * Internet chess server status
988      */
989     if (appData.icsActive) {
990         appData.matchMode = FALSE;
991         appData.matchGames = 0;
992 #if ZIPPY
993         appData.noChessProgram = !appData.zippyPlay;
994 #else
995         appData.zippyPlay = FALSE;
996         appData.zippyTalk = FALSE;
997         appData.noChessProgram = TRUE;
998 #endif
999         if (*appData.icsHelper != NULLCHAR) {
1000             appData.useTelnet = TRUE;
1001             appData.telnetProgram = appData.icsHelper;
1002         }
1003     } else {
1004         appData.zippyTalk = appData.zippyPlay = FALSE;
1005     }
1006
1007     /* [AS] Initialize pv info list [HGM] and game state */
1008     {
1009         int i, j;
1010
1011         for( i=0; i<=framePtr; i++ ) {
1012             pvInfoList[i].depth = -1;
1013             boards[i][EP_STATUS] = EP_NONE;
1014             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1015         }
1016     }
1017
1018     InitTimeControls();
1019
1020     /* [AS] Adjudication threshold */
1021     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1022
1023     InitEngine(&first, 0);
1024     InitEngine(&second, 1);
1025     CommonEngineInit();
1026
1027     pairing.which = "pairing"; // pairing engine
1028     pairing.pr = NoProc;
1029     pairing.isr = NULL;
1030     pairing.program = appData.pairingEngine;
1031     pairing.host = "localhost";
1032     pairing.dir = ".";
1033
1034     if (appData.icsActive) {
1035         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1036     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1037         appData.clockMode = FALSE;
1038         first.sendTime = second.sendTime = 0;
1039     }
1040
1041 #if ZIPPY
1042     /* Override some settings from environment variables, for backward
1043        compatibility.  Unfortunately it's not feasible to have the env
1044        vars just set defaults, at least in xboard.  Ugh.
1045     */
1046     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1047       ZippyInit();
1048     }
1049 #endif
1050
1051     if (!appData.icsActive) {
1052       char buf[MSG_SIZ];
1053       int len;
1054
1055       /* Check for variants that are supported only in ICS mode,
1056          or not at all.  Some that are accepted here nevertheless
1057          have bugs; see comments below.
1058       */
1059       VariantClass variant = StringToVariant(appData.variant);
1060       switch (variant) {
1061       case VariantBughouse:     /* need four players and two boards */
1062       case VariantKriegspiel:   /* need to hide pieces and move details */
1063         /* case VariantFischeRandom: (Fabien: moved below) */
1064         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1065         if( (len >= MSG_SIZ) && appData.debugMode )
1066           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1067
1068         DisplayFatalError(buf, 0, 2);
1069         return;
1070
1071       case VariantUnknown:
1072       case VariantLoadable:
1073       case Variant29:
1074       case Variant30:
1075       case Variant31:
1076       case Variant32:
1077       case Variant33:
1078       case Variant34:
1079       case Variant35:
1080       case Variant36:
1081       default:
1082         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1083         if( (len >= MSG_SIZ) && appData.debugMode )
1084           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1085
1086         DisplayFatalError(buf, 0, 2);
1087         return;
1088
1089       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1090       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1091       case VariantGothic:     /* [HGM] should work */
1092       case VariantCapablanca: /* [HGM] should work */
1093       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1094       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1095       case VariantKnightmate: /* [HGM] should work */
1096       case VariantCylinder:   /* [HGM] untested */
1097       case VariantFalcon:     /* [HGM] untested */
1098       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1099                                  offboard interposition not understood */
1100       case VariantNormal:     /* definitely works! */
1101       case VariantWildCastle: /* pieces not automatically shuffled */
1102       case VariantNoCastle:   /* pieces not automatically shuffled */
1103       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1104       case VariantLosers:     /* should work except for win condition,
1105                                  and doesn't know captures are mandatory */
1106       case VariantSuicide:    /* should work except for win condition,
1107                                  and doesn't know captures are mandatory */
1108       case VariantGiveaway:   /* should work except for win condition,
1109                                  and doesn't know captures are mandatory */
1110       case VariantTwoKings:   /* should work */
1111       case VariantAtomic:     /* should work except for win condition */
1112       case Variant3Check:     /* should work except for win condition */
1113       case VariantShatranj:   /* should work except for all win conditions */
1114       case VariantMakruk:     /* should work except for draw countdown */
1115       case VariantBerolina:   /* might work if TestLegality is off */
1116       case VariantCapaRandom: /* should work */
1117       case VariantJanus:      /* should work */
1118       case VariantSuper:      /* experimental */
1119       case VariantGreat:      /* experimental, requires legality testing to be off */
1120       case VariantSChess:     /* S-Chess, should work */
1121       case VariantGrand:      /* should work */
1122       case VariantSpartan:    /* should work */
1123         break;
1124       }
1125     }
1126
1127 }
1128
1129 int
1130 NextIntegerFromString (char ** str, long * value)
1131 {
1132     int result = -1;
1133     char * s = *str;
1134
1135     while( *s == ' ' || *s == '\t' ) {
1136         s++;
1137     }
1138
1139     *value = 0;
1140
1141     if( *s >= '0' && *s <= '9' ) {
1142         while( *s >= '0' && *s <= '9' ) {
1143             *value = *value * 10 + (*s - '0');
1144             s++;
1145         }
1146
1147         result = 0;
1148     }
1149
1150     *str = s;
1151
1152     return result;
1153 }
1154
1155 int
1156 NextTimeControlFromString (char ** str, long * value)
1157 {
1158     long temp;
1159     int result = NextIntegerFromString( str, &temp );
1160
1161     if( result == 0 ) {
1162         *value = temp * 60; /* Minutes */
1163         if( **str == ':' ) {
1164             (*str)++;
1165             result = NextIntegerFromString( str, &temp );
1166             *value += temp; /* Seconds */
1167         }
1168     }
1169
1170     return result;
1171 }
1172
1173 int
1174 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1175 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1176     int result = -1, type = 0; long temp, temp2;
1177
1178     if(**str != ':') return -1; // old params remain in force!
1179     (*str)++;
1180     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1181     if( NextIntegerFromString( str, &temp ) ) return -1;
1182     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1183
1184     if(**str != '/') {
1185         /* time only: incremental or sudden-death time control */
1186         if(**str == '+') { /* increment follows; read it */
1187             (*str)++;
1188             if(**str == '!') type = *(*str)++; // Bronstein TC
1189             if(result = NextIntegerFromString( str, &temp2)) return -1;
1190             *inc = temp2 * 1000;
1191             if(**str == '.') { // read fraction of increment
1192                 char *start = ++(*str);
1193                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1194                 temp2 *= 1000;
1195                 while(start++ < *str) temp2 /= 10;
1196                 *inc += temp2;
1197             }
1198         } else *inc = 0;
1199         *moves = 0; *tc = temp * 1000; *incType = type;
1200         return 0;
1201     }
1202
1203     (*str)++; /* classical time control */
1204     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1205
1206     if(result == 0) {
1207         *moves = temp;
1208         *tc    = temp2 * 1000;
1209         *inc   = 0;
1210         *incType = type;
1211     }
1212     return result;
1213 }
1214
1215 int
1216 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1217 {   /* [HGM] get time to add from the multi-session time-control string */
1218     int incType, moves=1; /* kludge to force reading of first session */
1219     long time, increment;
1220     char *s = tcString;
1221
1222     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1223     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1224     do {
1225         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1226         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1227         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1228         if(movenr == -1) return time;    /* last move before new session     */
1229         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1230         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1231         if(!moves) return increment;     /* current session is incremental   */
1232         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1233     } while(movenr >= -1);               /* try again for next session       */
1234
1235     return 0; // no new time quota on this move
1236 }
1237
1238 int
1239 ParseTimeControl (char *tc, float ti, int mps)
1240 {
1241   long tc1;
1242   long tc2;
1243   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1244   int min, sec=0;
1245
1246   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1247   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1248       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1249   if(ti > 0) {
1250
1251     if(mps)
1252       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1253     else 
1254       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1255   } else {
1256     if(mps)
1257       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1258     else 
1259       snprintf(buf, MSG_SIZ, ":%s", mytc);
1260   }
1261   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1262   
1263   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1264     return FALSE;
1265   }
1266
1267   if( *tc == '/' ) {
1268     /* Parse second time control */
1269     tc++;
1270
1271     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1272       return FALSE;
1273     }
1274
1275     if( tc2 == 0 ) {
1276       return FALSE;
1277     }
1278
1279     timeControl_2 = tc2 * 1000;
1280   }
1281   else {
1282     timeControl_2 = 0;
1283   }
1284
1285   if( tc1 == 0 ) {
1286     return FALSE;
1287   }
1288
1289   timeControl = tc1 * 1000;
1290
1291   if (ti >= 0) {
1292     timeIncrement = ti * 1000;  /* convert to ms */
1293     movesPerSession = 0;
1294   } else {
1295     timeIncrement = 0;
1296     movesPerSession = mps;
1297   }
1298   return TRUE;
1299 }
1300
1301 void
1302 InitBackEnd2 ()
1303 {
1304     if (appData.debugMode) {
1305         fprintf(debugFP, "%s\n", programVersion);
1306     }
1307
1308     set_cont_sequence(appData.wrapContSeq);
1309     if (appData.matchGames > 0) {
1310         appData.matchMode = TRUE;
1311     } else if (appData.matchMode) {
1312         appData.matchGames = 1;
1313     }
1314     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1315         appData.matchGames = appData.sameColorGames;
1316     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1317         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1318         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1319     }
1320     Reset(TRUE, FALSE);
1321     if (appData.noChessProgram || first.protocolVersion == 1) {
1322       InitBackEnd3();
1323     } else {
1324       /* kludge: allow timeout for initial "feature" commands */
1325       FreezeUI();
1326       DisplayMessage("", _("Starting chess program"));
1327       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1328     }
1329 }
1330
1331 int
1332 CalculateIndex (int index, int gameNr)
1333 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1334     int res;
1335     if(index > 0) return index; // fixed nmber
1336     if(index == 0) return 1;
1337     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1338     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1339     return res;
1340 }
1341
1342 int
1343 LoadGameOrPosition (int gameNr)
1344 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1345     if (*appData.loadGameFile != NULLCHAR) {
1346         if (!LoadGameFromFile(appData.loadGameFile,
1347                 CalculateIndex(appData.loadGameIndex, gameNr),
1348                               appData.loadGameFile, FALSE)) {
1349             DisplayFatalError(_("Bad game file"), 0, 1);
1350             return 0;
1351         }
1352     } else if (*appData.loadPositionFile != NULLCHAR) {
1353         if (!LoadPositionFromFile(appData.loadPositionFile,
1354                 CalculateIndex(appData.loadPositionIndex, gameNr),
1355                                   appData.loadPositionFile)) {
1356             DisplayFatalError(_("Bad position file"), 0, 1);
1357             return 0;
1358         }
1359     }
1360     return 1;
1361 }
1362
1363 void
1364 ReserveGame (int gameNr, char resChar)
1365 {
1366     FILE *tf = fopen(appData.tourneyFile, "r+");
1367     char *p, *q, c, buf[MSG_SIZ];
1368     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1369     safeStrCpy(buf, lastMsg, MSG_SIZ);
1370     DisplayMessage(_("Pick new game"), "");
1371     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1372     ParseArgsFromFile(tf);
1373     p = q = appData.results;
1374     if(appData.debugMode) {
1375       char *r = appData.participants;
1376       fprintf(debugFP, "results = '%s'\n", p);
1377       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1378       fprintf(debugFP, "\n");
1379     }
1380     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1381     nextGame = q - p;
1382     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1383     safeStrCpy(q, p, strlen(p) + 2);
1384     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1385     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1386     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1387         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1388         q[nextGame] = '*';
1389     }
1390     fseek(tf, -(strlen(p)+4), SEEK_END);
1391     c = fgetc(tf);
1392     if(c != '"') // depending on DOS or Unix line endings we can be one off
1393          fseek(tf, -(strlen(p)+2), SEEK_END);
1394     else fseek(tf, -(strlen(p)+3), SEEK_END);
1395     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1396     DisplayMessage(buf, "");
1397     free(p); appData.results = q;
1398     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1399        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1400       int round = appData.defaultMatchGames * appData.tourneyType;
1401       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1402          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1403         UnloadEngine(&first);  // next game belongs to other pairing;
1404         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1405     }
1406     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d, procs=(%x,%x)\n", nextGame, gameNr, first.pr, second.pr);
1407 }
1408
1409 void
1410 MatchEvent (int mode)
1411 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1412         int dummy;
1413         if(matchMode) { // already in match mode: switch it off
1414             abortMatch = TRUE;
1415             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1416             return;
1417         }
1418 //      if(gameMode != BeginningOfGame) {
1419 //          DisplayError(_("You can only start a match from the initial position."), 0);
1420 //          return;
1421 //      }
1422         abortMatch = FALSE;
1423         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1424         /* Set up machine vs. machine match */
1425         nextGame = 0;
1426         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1427         if(appData.tourneyFile[0]) {
1428             ReserveGame(-1, 0);
1429             if(nextGame > appData.matchGames) {
1430                 char buf[MSG_SIZ];
1431                 if(strchr(appData.results, '*') == NULL) {
1432                     FILE *f;
1433                     appData.tourneyCycles++;
1434                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1435                         fclose(f);
1436                         NextTourneyGame(-1, &dummy);
1437                         ReserveGame(-1, 0);
1438                         if(nextGame <= appData.matchGames) {
1439                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1440                             matchMode = mode;
1441                             ScheduleDelayedEvent(NextMatchGame, 10000);
1442                             return;
1443                         }
1444                     }
1445                 }
1446                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1447                 DisplayError(buf, 0);
1448                 appData.tourneyFile[0] = 0;
1449                 return;
1450             }
1451         } else
1452         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1453             DisplayFatalError(_("Can't have a match with no chess programs"),
1454                               0, 2);
1455             return;
1456         }
1457         matchMode = mode;
1458         matchGame = roundNr = 1;
1459         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1460         NextMatchGame();
1461 }
1462
1463 void
1464 InitBackEnd3 P((void))
1465 {
1466     GameMode initialMode;
1467     char buf[MSG_SIZ];
1468     int err, len;
1469
1470     InitChessProgram(&first, startedFromSetupPosition);
1471
1472     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1473         free(programVersion);
1474         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1475         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1476     }
1477
1478     if (appData.icsActive) {
1479 #ifdef WIN32
1480         /* [DM] Make a console window if needed [HGM] merged ifs */
1481         ConsoleCreate();
1482 #endif
1483         err = establish();
1484         if (err != 0)
1485           {
1486             if (*appData.icsCommPort != NULLCHAR)
1487               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1488                              appData.icsCommPort);
1489             else
1490               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1491                         appData.icsHost, appData.icsPort);
1492
1493             if( (len >= MSG_SIZ) && appData.debugMode )
1494               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1495
1496             DisplayFatalError(buf, err, 1);
1497             return;
1498         }
1499         SetICSMode();
1500         telnetISR =
1501           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1502         fromUserISR =
1503           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1504         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1505             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1506     } else if (appData.noChessProgram) {
1507         SetNCPMode();
1508     } else {
1509         SetGNUMode();
1510     }
1511
1512     if (*appData.cmailGameName != NULLCHAR) {
1513         SetCmailMode();
1514         OpenLoopback(&cmailPR);
1515         cmailISR =
1516           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1517     }
1518
1519     ThawUI();
1520     DisplayMessage("", "");
1521     if (StrCaseCmp(appData.initialMode, "") == 0) {
1522       initialMode = BeginningOfGame;
1523       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1524         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1525         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1526         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1527         ModeHighlight();
1528       }
1529     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1530       initialMode = TwoMachinesPlay;
1531     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1532       initialMode = AnalyzeFile;
1533     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1534       initialMode = AnalyzeMode;
1535     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1536       initialMode = MachinePlaysWhite;
1537     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1538       initialMode = MachinePlaysBlack;
1539     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1540       initialMode = EditGame;
1541     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1542       initialMode = EditPosition;
1543     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1544       initialMode = Training;
1545     } else {
1546       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1547       if( (len >= MSG_SIZ) && appData.debugMode )
1548         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1549
1550       DisplayFatalError(buf, 0, 2);
1551       return;
1552     }
1553
1554     if (appData.matchMode) {
1555         if(appData.tourneyFile[0]) { // start tourney from command line
1556             FILE *f;
1557             if(f = fopen(appData.tourneyFile, "r")) {
1558                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1559                 fclose(f);
1560                 appData.clockMode = TRUE;
1561                 SetGNUMode();
1562             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1563         }
1564         MatchEvent(TRUE);
1565     } else if (*appData.cmailGameName != NULLCHAR) {
1566         /* Set up cmail mode */
1567         ReloadCmailMsgEvent(TRUE);
1568     } else {
1569         /* Set up other modes */
1570         if (initialMode == AnalyzeFile) {
1571           if (*appData.loadGameFile == NULLCHAR) {
1572             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1573             return;
1574           }
1575         }
1576         if (*appData.loadGameFile != NULLCHAR) {
1577             (void) LoadGameFromFile(appData.loadGameFile,
1578                                     appData.loadGameIndex,
1579                                     appData.loadGameFile, TRUE);
1580         } else if (*appData.loadPositionFile != NULLCHAR) {
1581             (void) LoadPositionFromFile(appData.loadPositionFile,
1582                                         appData.loadPositionIndex,
1583                                         appData.loadPositionFile);
1584             /* [HGM] try to make self-starting even after FEN load */
1585             /* to allow automatic setup of fairy variants with wtm */
1586             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1587                 gameMode = BeginningOfGame;
1588                 setboardSpoiledMachineBlack = 1;
1589             }
1590             /* [HGM] loadPos: make that every new game uses the setup */
1591             /* from file as long as we do not switch variant          */
1592             if(!blackPlaysFirst) {
1593                 startedFromPositionFile = TRUE;
1594                 CopyBoard(filePosition, boards[0]);
1595             }
1596         }
1597         if (initialMode == AnalyzeMode) {
1598           if (appData.noChessProgram) {
1599             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1600             return;
1601           }
1602           if (appData.icsActive) {
1603             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1604             return;
1605           }
1606           AnalyzeModeEvent();
1607         } else if (initialMode == AnalyzeFile) {
1608           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1609           ShowThinkingEvent();
1610           AnalyzeFileEvent();
1611           AnalysisPeriodicEvent(1);
1612         } else if (initialMode == MachinePlaysWhite) {
1613           if (appData.noChessProgram) {
1614             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1615                               0, 2);
1616             return;
1617           }
1618           if (appData.icsActive) {
1619             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1620                               0, 2);
1621             return;
1622           }
1623           MachineWhiteEvent();
1624         } else if (initialMode == MachinePlaysBlack) {
1625           if (appData.noChessProgram) {
1626             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1627                               0, 2);
1628             return;
1629           }
1630           if (appData.icsActive) {
1631             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1632                               0, 2);
1633             return;
1634           }
1635           MachineBlackEvent();
1636         } else if (initialMode == TwoMachinesPlay) {
1637           if (appData.noChessProgram) {
1638             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1639                               0, 2);
1640             return;
1641           }
1642           if (appData.icsActive) {
1643             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1644                               0, 2);
1645             return;
1646           }
1647           TwoMachinesEvent();
1648         } else if (initialMode == EditGame) {
1649           EditGameEvent();
1650         } else if (initialMode == EditPosition) {
1651           EditPositionEvent();
1652         } else if (initialMode == Training) {
1653           if (*appData.loadGameFile == NULLCHAR) {
1654             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1655             return;
1656           }
1657           TrainingEvent();
1658         }
1659     }
1660 }
1661
1662 void
1663 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1664 {
1665     DisplayBook(current+1);
1666
1667     MoveHistorySet( movelist, first, last, current, pvInfoList );
1668
1669     EvalGraphSet( first, last, current, pvInfoList );
1670
1671     MakeEngineOutputTitle();
1672 }
1673
1674 /*
1675  * Establish will establish a contact to a remote host.port.
1676  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1677  *  used to talk to the host.
1678  * Returns 0 if okay, error code if not.
1679  */
1680 int
1681 establish ()
1682 {
1683     char buf[MSG_SIZ];
1684
1685     if (*appData.icsCommPort != NULLCHAR) {
1686         /* Talk to the host through a serial comm port */
1687         return OpenCommPort(appData.icsCommPort, &icsPR);
1688
1689     } else if (*appData.gateway != NULLCHAR) {
1690         if (*appData.remoteShell == NULLCHAR) {
1691             /* Use the rcmd protocol to run telnet program on a gateway host */
1692             snprintf(buf, sizeof(buf), "%s %s %s",
1693                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1694             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1695
1696         } else {
1697             /* Use the rsh program to run telnet program on a gateway host */
1698             if (*appData.remoteUser == NULLCHAR) {
1699                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1700                         appData.gateway, appData.telnetProgram,
1701                         appData.icsHost, appData.icsPort);
1702             } else {
1703                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1704                         appData.remoteShell, appData.gateway,
1705                         appData.remoteUser, appData.telnetProgram,
1706                         appData.icsHost, appData.icsPort);
1707             }
1708             return StartChildProcess(buf, "", &icsPR);
1709
1710         }
1711     } else if (appData.useTelnet) {
1712         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1713
1714     } else {
1715         /* TCP socket interface differs somewhat between
1716            Unix and NT; handle details in the front end.
1717            */
1718         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1719     }
1720 }
1721
1722 void
1723 EscapeExpand (char *p, char *q)
1724 {       // [HGM] initstring: routine to shape up string arguments
1725         while(*p++ = *q++) if(p[-1] == '\\')
1726             switch(*q++) {
1727                 case 'n': p[-1] = '\n'; break;
1728                 case 'r': p[-1] = '\r'; break;
1729                 case 't': p[-1] = '\t'; break;
1730                 case '\\': p[-1] = '\\'; break;
1731                 case 0: *p = 0; return;
1732                 default: p[-1] = q[-1]; break;
1733             }
1734 }
1735
1736 void
1737 show_bytes (FILE *fp, char *buf, int count)
1738 {
1739     while (count--) {
1740         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1741             fprintf(fp, "\\%03o", *buf & 0xff);
1742         } else {
1743             putc(*buf, fp);
1744         }
1745         buf++;
1746     }
1747     fflush(fp);
1748 }
1749
1750 /* Returns an errno value */
1751 int
1752 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1753 {
1754     char buf[8192], *p, *q, *buflim;
1755     int left, newcount, outcount;
1756
1757     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1758         *appData.gateway != NULLCHAR) {
1759         if (appData.debugMode) {
1760             fprintf(debugFP, ">ICS: ");
1761             show_bytes(debugFP, message, count);
1762             fprintf(debugFP, "\n");
1763         }
1764         return OutputToProcess(pr, message, count, outError);
1765     }
1766
1767     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1768     p = message;
1769     q = buf;
1770     left = count;
1771     newcount = 0;
1772     while (left) {
1773         if (q >= buflim) {
1774             if (appData.debugMode) {
1775                 fprintf(debugFP, ">ICS: ");
1776                 show_bytes(debugFP, buf, newcount);
1777                 fprintf(debugFP, "\n");
1778             }
1779             outcount = OutputToProcess(pr, buf, newcount, outError);
1780             if (outcount < newcount) return -1; /* to be sure */
1781             q = buf;
1782             newcount = 0;
1783         }
1784         if (*p == '\n') {
1785             *q++ = '\r';
1786             newcount++;
1787         } else if (((unsigned char) *p) == TN_IAC) {
1788             *q++ = (char) TN_IAC;
1789             newcount ++;
1790         }
1791         *q++ = *p++;
1792         newcount++;
1793         left--;
1794     }
1795     if (appData.debugMode) {
1796         fprintf(debugFP, ">ICS: ");
1797         show_bytes(debugFP, buf, newcount);
1798         fprintf(debugFP, "\n");
1799     }
1800     outcount = OutputToProcess(pr, buf, newcount, outError);
1801     if (outcount < newcount) return -1; /* to be sure */
1802     return count;
1803 }
1804
1805 void
1806 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1807 {
1808     int outError, outCount;
1809     static int gotEof = 0;
1810
1811     /* Pass data read from player on to ICS */
1812     if (count > 0) {
1813         gotEof = 0;
1814         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1815         if (outCount < count) {
1816             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1817         }
1818     } else if (count < 0) {
1819         RemoveInputSource(isr);
1820         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1821     } else if (gotEof++ > 0) {
1822         RemoveInputSource(isr);
1823         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1824     }
1825 }
1826
1827 void
1828 KeepAlive ()
1829 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1830     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1831     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1832     SendToICS("date\n");
1833     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1834 }
1835
1836 /* added routine for printf style output to ics */
1837 void
1838 ics_printf (char *format, ...)
1839 {
1840     char buffer[MSG_SIZ];
1841     va_list args;
1842
1843     va_start(args, format);
1844     vsnprintf(buffer, sizeof(buffer), format, args);
1845     buffer[sizeof(buffer)-1] = '\0';
1846     SendToICS(buffer);
1847     va_end(args);
1848 }
1849
1850 void
1851 SendToICS (char *s)
1852 {
1853     int count, outCount, outError;
1854
1855     if (icsPR == NoProc) return;
1856
1857     count = strlen(s);
1858     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1859     if (outCount < count) {
1860         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1861     }
1862 }
1863
1864 /* This is used for sending logon scripts to the ICS. Sending
1865    without a delay causes problems when using timestamp on ICC
1866    (at least on my machine). */
1867 void
1868 SendToICSDelayed (char *s, long msdelay)
1869 {
1870     int count, outCount, outError;
1871
1872     if (icsPR == NoProc) return;
1873
1874     count = strlen(s);
1875     if (appData.debugMode) {
1876         fprintf(debugFP, ">ICS: ");
1877         show_bytes(debugFP, s, count);
1878         fprintf(debugFP, "\n");
1879     }
1880     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1881                                       msdelay);
1882     if (outCount < count) {
1883         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1884     }
1885 }
1886
1887
1888 /* Remove all highlighting escape sequences in s
1889    Also deletes any suffix starting with '('
1890    */
1891 char *
1892 StripHighlightAndTitle (char *s)
1893 {
1894     static char retbuf[MSG_SIZ];
1895     char *p = retbuf;
1896
1897     while (*s != NULLCHAR) {
1898         while (*s == '\033') {
1899             while (*s != NULLCHAR && !isalpha(*s)) s++;
1900             if (*s != NULLCHAR) s++;
1901         }
1902         while (*s != NULLCHAR && *s != '\033') {
1903             if (*s == '(' || *s == '[') {
1904                 *p = NULLCHAR;
1905                 return retbuf;
1906             }
1907             *p++ = *s++;
1908         }
1909     }
1910     *p = NULLCHAR;
1911     return retbuf;
1912 }
1913
1914 /* Remove all highlighting escape sequences in s */
1915 char *
1916 StripHighlight (char *s)
1917 {
1918     static char retbuf[MSG_SIZ];
1919     char *p = retbuf;
1920
1921     while (*s != NULLCHAR) {
1922         while (*s == '\033') {
1923             while (*s != NULLCHAR && !isalpha(*s)) s++;
1924             if (*s != NULLCHAR) s++;
1925         }
1926         while (*s != NULLCHAR && *s != '\033') {
1927             *p++ = *s++;
1928         }
1929     }
1930     *p = NULLCHAR;
1931     return retbuf;
1932 }
1933
1934 char *variantNames[] = VARIANT_NAMES;
1935 char *
1936 VariantName (VariantClass v)
1937 {
1938     return variantNames[v];
1939 }
1940
1941
1942 /* Identify a variant from the strings the chess servers use or the
1943    PGN Variant tag names we use. */
1944 VariantClass
1945 StringToVariant (char *e)
1946 {
1947     char *p;
1948     int wnum = -1;
1949     VariantClass v = VariantNormal;
1950     int i, found = FALSE;
1951     char buf[MSG_SIZ];
1952     int len;
1953
1954     if (!e) return v;
1955
1956     /* [HGM] skip over optional board-size prefixes */
1957     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1958         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1959         while( *e++ != '_');
1960     }
1961
1962     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1963         v = VariantNormal;
1964         found = TRUE;
1965     } else
1966     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1967       if (StrCaseStr(e, variantNames[i])) {
1968         v = (VariantClass) i;
1969         found = TRUE;
1970         break;
1971       }
1972     }
1973
1974     if (!found) {
1975       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1976           || StrCaseStr(e, "wild/fr")
1977           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1978         v = VariantFischeRandom;
1979       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1980                  (i = 1, p = StrCaseStr(e, "w"))) {
1981         p += i;
1982         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1983         if (isdigit(*p)) {
1984           wnum = atoi(p);
1985         } else {
1986           wnum = -1;
1987         }
1988         switch (wnum) {
1989         case 0: /* FICS only, actually */
1990         case 1:
1991           /* Castling legal even if K starts on d-file */
1992           v = VariantWildCastle;
1993           break;
1994         case 2:
1995         case 3:
1996         case 4:
1997           /* Castling illegal even if K & R happen to start in
1998              normal positions. */
1999           v = VariantNoCastle;
2000           break;
2001         case 5:
2002         case 7:
2003         case 8:
2004         case 10:
2005         case 11:
2006         case 12:
2007         case 13:
2008         case 14:
2009         case 15:
2010         case 18:
2011         case 19:
2012           /* Castling legal iff K & R start in normal positions */
2013           v = VariantNormal;
2014           break;
2015         case 6:
2016         case 20:
2017         case 21:
2018           /* Special wilds for position setup; unclear what to do here */
2019           v = VariantLoadable;
2020           break;
2021         case 9:
2022           /* Bizarre ICC game */
2023           v = VariantTwoKings;
2024           break;
2025         case 16:
2026           v = VariantKriegspiel;
2027           break;
2028         case 17:
2029           v = VariantLosers;
2030           break;
2031         case 22:
2032           v = VariantFischeRandom;
2033           break;
2034         case 23:
2035           v = VariantCrazyhouse;
2036           break;
2037         case 24:
2038           v = VariantBughouse;
2039           break;
2040         case 25:
2041           v = Variant3Check;
2042           break;
2043         case 26:
2044           /* Not quite the same as FICS suicide! */
2045           v = VariantGiveaway;
2046           break;
2047         case 27:
2048           v = VariantAtomic;
2049           break;
2050         case 28:
2051           v = VariantShatranj;
2052           break;
2053
2054         /* Temporary names for future ICC types.  The name *will* change in
2055            the next xboard/WinBoard release after ICC defines it. */
2056         case 29:
2057           v = Variant29;
2058           break;
2059         case 30:
2060           v = Variant30;
2061           break;
2062         case 31:
2063           v = Variant31;
2064           break;
2065         case 32:
2066           v = Variant32;
2067           break;
2068         case 33:
2069           v = Variant33;
2070           break;
2071         case 34:
2072           v = Variant34;
2073           break;
2074         case 35:
2075           v = Variant35;
2076           break;
2077         case 36:
2078           v = Variant36;
2079           break;
2080         case 37:
2081           v = VariantShogi;
2082           break;
2083         case 38:
2084           v = VariantXiangqi;
2085           break;
2086         case 39:
2087           v = VariantCourier;
2088           break;
2089         case 40:
2090           v = VariantGothic;
2091           break;
2092         case 41:
2093           v = VariantCapablanca;
2094           break;
2095         case 42:
2096           v = VariantKnightmate;
2097           break;
2098         case 43:
2099           v = VariantFairy;
2100           break;
2101         case 44:
2102           v = VariantCylinder;
2103           break;
2104         case 45:
2105           v = VariantFalcon;
2106           break;
2107         case 46:
2108           v = VariantCapaRandom;
2109           break;
2110         case 47:
2111           v = VariantBerolina;
2112           break;
2113         case 48:
2114           v = VariantJanus;
2115           break;
2116         case 49:
2117           v = VariantSuper;
2118           break;
2119         case 50:
2120           v = VariantGreat;
2121           break;
2122         case -1:
2123           /* Found "wild" or "w" in the string but no number;
2124              must assume it's normal chess. */
2125           v = VariantNormal;
2126           break;
2127         default:
2128           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2129           if( (len >= MSG_SIZ) && appData.debugMode )
2130             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2131
2132           DisplayError(buf, 0);
2133           v = VariantUnknown;
2134           break;
2135         }
2136       }
2137     }
2138     if (appData.debugMode) {
2139       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2140               e, wnum, VariantName(v));
2141     }
2142     return v;
2143 }
2144
2145 static int leftover_start = 0, leftover_len = 0;
2146 char star_match[STAR_MATCH_N][MSG_SIZ];
2147
2148 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2149    advance *index beyond it, and set leftover_start to the new value of
2150    *index; else return FALSE.  If pattern contains the character '*', it
2151    matches any sequence of characters not containing '\r', '\n', or the
2152    character following the '*' (if any), and the matched sequence(s) are
2153    copied into star_match.
2154    */
2155 int
2156 looking_at ( char *buf, int *index, char *pattern)
2157 {
2158     char *bufp = &buf[*index], *patternp = pattern;
2159     int star_count = 0;
2160     char *matchp = star_match[0];
2161
2162     for (;;) {
2163         if (*patternp == NULLCHAR) {
2164             *index = leftover_start = bufp - buf;
2165             *matchp = NULLCHAR;
2166             return TRUE;
2167         }
2168         if (*bufp == NULLCHAR) return FALSE;
2169         if (*patternp == '*') {
2170             if (*bufp == *(patternp + 1)) {
2171                 *matchp = NULLCHAR;
2172                 matchp = star_match[++star_count];
2173                 patternp += 2;
2174                 bufp++;
2175                 continue;
2176             } else if (*bufp == '\n' || *bufp == '\r') {
2177                 patternp++;
2178                 if (*patternp == NULLCHAR)
2179                   continue;
2180                 else
2181                   return FALSE;
2182             } else {
2183                 *matchp++ = *bufp++;
2184                 continue;
2185             }
2186         }
2187         if (*patternp != *bufp) return FALSE;
2188         patternp++;
2189         bufp++;
2190     }
2191 }
2192
2193 void
2194 SendToPlayer (char *data, int length)
2195 {
2196     int error, outCount;
2197     outCount = OutputToProcess(NoProc, data, length, &error);
2198     if (outCount < length) {
2199         DisplayFatalError(_("Error writing to display"), error, 1);
2200     }
2201 }
2202
2203 void
2204 PackHolding (char packed[], char *holding)
2205 {
2206     char *p = holding;
2207     char *q = packed;
2208     int runlength = 0;
2209     int curr = 9999;
2210     do {
2211         if (*p == curr) {
2212             runlength++;
2213         } else {
2214             switch (runlength) {
2215               case 0:
2216                 break;
2217               case 1:
2218                 *q++ = curr;
2219                 break;
2220               case 2:
2221                 *q++ = curr;
2222                 *q++ = curr;
2223                 break;
2224               default:
2225                 sprintf(q, "%d", runlength);
2226                 while (*q) q++;
2227                 *q++ = curr;
2228                 break;
2229             }
2230             runlength = 1;
2231             curr = *p;
2232         }
2233     } while (*p++);
2234     *q = NULLCHAR;
2235 }
2236
2237 /* Telnet protocol requests from the front end */
2238 void
2239 TelnetRequest (unsigned char ddww, unsigned char option)
2240 {
2241     unsigned char msg[3];
2242     int outCount, outError;
2243
2244     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2245
2246     if (appData.debugMode) {
2247         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2248         switch (ddww) {
2249           case TN_DO:
2250             ddwwStr = "DO";
2251             break;
2252           case TN_DONT:
2253             ddwwStr = "DONT";
2254             break;
2255           case TN_WILL:
2256             ddwwStr = "WILL";
2257             break;
2258           case TN_WONT:
2259             ddwwStr = "WONT";
2260             break;
2261           default:
2262             ddwwStr = buf1;
2263             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2264             break;
2265         }
2266         switch (option) {
2267           case TN_ECHO:
2268             optionStr = "ECHO";
2269             break;
2270           default:
2271             optionStr = buf2;
2272             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2273             break;
2274         }
2275         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2276     }
2277     msg[0] = TN_IAC;
2278     msg[1] = ddww;
2279     msg[2] = option;
2280     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2281     if (outCount < 3) {
2282         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2283     }
2284 }
2285
2286 void
2287 DoEcho ()
2288 {
2289     if (!appData.icsActive) return;
2290     TelnetRequest(TN_DO, TN_ECHO);
2291 }
2292
2293 void
2294 DontEcho ()
2295 {
2296     if (!appData.icsActive) return;
2297     TelnetRequest(TN_DONT, TN_ECHO);
2298 }
2299
2300 void
2301 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2302 {
2303     /* put the holdings sent to us by the server on the board holdings area */
2304     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2305     char p;
2306     ChessSquare piece;
2307
2308     if(gameInfo.holdingsWidth < 2)  return;
2309     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2310         return; // prevent overwriting by pre-board holdings
2311
2312     if( (int)lowestPiece >= BlackPawn ) {
2313         holdingsColumn = 0;
2314         countsColumn = 1;
2315         holdingsStartRow = BOARD_HEIGHT-1;
2316         direction = -1;
2317     } else {
2318         holdingsColumn = BOARD_WIDTH-1;
2319         countsColumn = BOARD_WIDTH-2;
2320         holdingsStartRow = 0;
2321         direction = 1;
2322     }
2323
2324     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2325         board[i][holdingsColumn] = EmptySquare;
2326         board[i][countsColumn]   = (ChessSquare) 0;
2327     }
2328     while( (p=*holdings++) != NULLCHAR ) {
2329         piece = CharToPiece( ToUpper(p) );
2330         if(piece == EmptySquare) continue;
2331         /*j = (int) piece - (int) WhitePawn;*/
2332         j = PieceToNumber(piece);
2333         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2334         if(j < 0) continue;               /* should not happen */
2335         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2336         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2337         board[holdingsStartRow+j*direction][countsColumn]++;
2338     }
2339 }
2340
2341
2342 void
2343 VariantSwitch (Board board, VariantClass newVariant)
2344 {
2345    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2346    static Board oldBoard;
2347
2348    startedFromPositionFile = FALSE;
2349    if(gameInfo.variant == newVariant) return;
2350
2351    /* [HGM] This routine is called each time an assignment is made to
2352     * gameInfo.variant during a game, to make sure the board sizes
2353     * are set to match the new variant. If that means adding or deleting
2354     * holdings, we shift the playing board accordingly
2355     * This kludge is needed because in ICS observe mode, we get boards
2356     * of an ongoing game without knowing the variant, and learn about the
2357     * latter only later. This can be because of the move list we requested,
2358     * in which case the game history is refilled from the beginning anyway,
2359     * but also when receiving holdings of a crazyhouse game. In the latter
2360     * case we want to add those holdings to the already received position.
2361     */
2362
2363
2364    if (appData.debugMode) {
2365      fprintf(debugFP, "Switch board from %s to %s\n",
2366              VariantName(gameInfo.variant), VariantName(newVariant));
2367      setbuf(debugFP, NULL);
2368    }
2369    shuffleOpenings = 0;       /* [HGM] shuffle */
2370    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2371    switch(newVariant)
2372      {
2373      case VariantShogi:
2374        newWidth = 9;  newHeight = 9;
2375        gameInfo.holdingsSize = 7;
2376      case VariantBughouse:
2377      case VariantCrazyhouse:
2378        newHoldingsWidth = 2; break;
2379      case VariantGreat:
2380        newWidth = 10;
2381      case VariantSuper:
2382        newHoldingsWidth = 2;
2383        gameInfo.holdingsSize = 8;
2384        break;
2385      case VariantGothic:
2386      case VariantCapablanca:
2387      case VariantCapaRandom:
2388        newWidth = 10;
2389      default:
2390        newHoldingsWidth = gameInfo.holdingsSize = 0;
2391      };
2392
2393    if(newWidth  != gameInfo.boardWidth  ||
2394       newHeight != gameInfo.boardHeight ||
2395       newHoldingsWidth != gameInfo.holdingsWidth ) {
2396
2397      /* shift position to new playing area, if needed */
2398      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2399        for(i=0; i<BOARD_HEIGHT; i++)
2400          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2401            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2402              board[i][j];
2403        for(i=0; i<newHeight; i++) {
2404          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2405          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2406        }
2407      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2408        for(i=0; i<BOARD_HEIGHT; i++)
2409          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2410            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2411              board[i][j];
2412      }
2413      gameInfo.boardWidth  = newWidth;
2414      gameInfo.boardHeight = newHeight;
2415      gameInfo.holdingsWidth = newHoldingsWidth;
2416      gameInfo.variant = newVariant;
2417      InitDrawingSizes(-2, 0);
2418    } else gameInfo.variant = newVariant;
2419    CopyBoard(oldBoard, board);   // remember correctly formatted board
2420      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2421    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2422 }
2423
2424 static int loggedOn = FALSE;
2425
2426 /*-- Game start info cache: --*/
2427 int gs_gamenum;
2428 char gs_kind[MSG_SIZ];
2429 static char player1Name[128] = "";
2430 static char player2Name[128] = "";
2431 static char cont_seq[] = "\n\\   ";
2432 static int player1Rating = -1;
2433 static int player2Rating = -1;
2434 /*----------------------------*/
2435
2436 ColorClass curColor = ColorNormal;
2437 int suppressKibitz = 0;
2438
2439 // [HGM] seekgraph
2440 Boolean soughtPending = FALSE;
2441 Boolean seekGraphUp;
2442 #define MAX_SEEK_ADS 200
2443 #define SQUARE 0x80
2444 char *seekAdList[MAX_SEEK_ADS];
2445 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2446 float tcList[MAX_SEEK_ADS];
2447 char colorList[MAX_SEEK_ADS];
2448 int nrOfSeekAds = 0;
2449 int minRating = 1010, maxRating = 2800;
2450 int hMargin = 10, vMargin = 20, h, w;
2451 extern int squareSize, lineGap;
2452
2453 void
2454 PlotSeekAd (int i)
2455 {
2456         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2457         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2458         if(r < minRating+100 && r >=0 ) r = minRating+100;
2459         if(r > maxRating) r = maxRating;
2460         if(tc < 1.) tc = 1.;
2461         if(tc > 95.) tc = 95.;
2462         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2463         y = ((double)r - minRating)/(maxRating - minRating)
2464             * (h-vMargin-squareSize/8-1) + vMargin;
2465         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2466         if(strstr(seekAdList[i], " u ")) color = 1;
2467         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2468            !strstr(seekAdList[i], "bullet") &&
2469            !strstr(seekAdList[i], "blitz") &&
2470            !strstr(seekAdList[i], "standard") ) color = 2;
2471         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2472         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2473 }
2474
2475 void
2476 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2477 {
2478         char buf[MSG_SIZ], *ext = "";
2479         VariantClass v = StringToVariant(type);
2480         if(strstr(type, "wild")) {
2481             ext = type + 4; // append wild number
2482             if(v == VariantFischeRandom) type = "chess960"; else
2483             if(v == VariantLoadable) type = "setup"; else
2484             type = VariantName(v);
2485         }
2486         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2487         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2488             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2489             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2490             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2491             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2492             seekNrList[nrOfSeekAds] = nr;
2493             zList[nrOfSeekAds] = 0;
2494             seekAdList[nrOfSeekAds++] = StrSave(buf);
2495             if(plot) PlotSeekAd(nrOfSeekAds-1);
2496         }
2497 }
2498
2499 void
2500 EraseSeekDot (int i)
2501 {
2502     int x = xList[i], y = yList[i], d=squareSize/4, k;
2503     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2504     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2505     // now replot every dot that overlapped
2506     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2507         int xx = xList[k], yy = yList[k];
2508         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2509             DrawSeekDot(xx, yy, colorList[k]);
2510     }
2511 }
2512
2513 void
2514 RemoveSeekAd (int nr)
2515 {
2516         int i;
2517         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2518             EraseSeekDot(i);
2519             if(seekAdList[i]) free(seekAdList[i]);
2520             seekAdList[i] = seekAdList[--nrOfSeekAds];
2521             seekNrList[i] = seekNrList[nrOfSeekAds];
2522             ratingList[i] = ratingList[nrOfSeekAds];
2523             colorList[i]  = colorList[nrOfSeekAds];
2524             tcList[i] = tcList[nrOfSeekAds];
2525             xList[i]  = xList[nrOfSeekAds];
2526             yList[i]  = yList[nrOfSeekAds];
2527             zList[i]  = zList[nrOfSeekAds];
2528             seekAdList[nrOfSeekAds] = NULL;
2529             break;
2530         }
2531 }
2532
2533 Boolean
2534 MatchSoughtLine (char *line)
2535 {
2536     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2537     int nr, base, inc, u=0; char dummy;
2538
2539     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2540        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2541        (u=1) &&
2542        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2543         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2544         // match: compact and save the line
2545         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2546         return TRUE;
2547     }
2548     return FALSE;
2549 }
2550
2551 int
2552 DrawSeekGraph ()
2553 {
2554     int i;
2555     if(!seekGraphUp) return FALSE;
2556     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2557     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2558
2559     DrawSeekBackground(0, 0, w, h);
2560     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2561     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2562     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2563         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2564         yy = h-1-yy;
2565         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2566         if(i%500 == 0) {
2567             char buf[MSG_SIZ];
2568             snprintf(buf, MSG_SIZ, "%d", i);
2569             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2570         }
2571     }
2572     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2573     for(i=1; i<100; i+=(i<10?1:5)) {
2574         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2575         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2576         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2577             char buf[MSG_SIZ];
2578             snprintf(buf, MSG_SIZ, "%d", i);
2579             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2580         }
2581     }
2582     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2583     return TRUE;
2584 }
2585
2586 int
2587 SeekGraphClick (ClickType click, int x, int y, int moving)
2588 {
2589     static int lastDown = 0, displayed = 0, lastSecond;
2590     if(y < 0) return FALSE;
2591     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2592         if(click == Release || moving) return FALSE;
2593         nrOfSeekAds = 0;
2594         soughtPending = TRUE;
2595         SendToICS(ics_prefix);
2596         SendToICS("sought\n"); // should this be "sought all"?
2597     } else { // issue challenge based on clicked ad
2598         int dist = 10000; int i, closest = 0, second = 0;
2599         for(i=0; i<nrOfSeekAds; i++) {
2600             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2601             if(d < dist) { dist = d; closest = i; }
2602             second += (d - zList[i] < 120); // count in-range ads
2603             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2604         }
2605         if(dist < 120) {
2606             char buf[MSG_SIZ];
2607             second = (second > 1);
2608             if(displayed != closest || second != lastSecond) {
2609                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2610                 lastSecond = second; displayed = closest;
2611             }
2612             if(click == Press) {
2613                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2614                 lastDown = closest;
2615                 return TRUE;
2616             } // on press 'hit', only show info
2617             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2618             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2619             SendToICS(ics_prefix);
2620             SendToICS(buf);
2621             return TRUE; // let incoming board of started game pop down the graph
2622         } else if(click == Release) { // release 'miss' is ignored
2623             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2624             if(moving == 2) { // right up-click
2625                 nrOfSeekAds = 0; // refresh graph
2626                 soughtPending = TRUE;
2627                 SendToICS(ics_prefix);
2628                 SendToICS("sought\n"); // should this be "sought all"?
2629             }
2630             return TRUE;
2631         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2632         // press miss or release hit 'pop down' seek graph
2633         seekGraphUp = FALSE;
2634         DrawPosition(TRUE, NULL);
2635     }
2636     return TRUE;
2637 }
2638
2639 void
2640 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2641 {
2642 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2643 #define STARTED_NONE 0
2644 #define STARTED_MOVES 1
2645 #define STARTED_BOARD 2
2646 #define STARTED_OBSERVE 3
2647 #define STARTED_HOLDINGS 4
2648 #define STARTED_CHATTER 5
2649 #define STARTED_COMMENT 6
2650 #define STARTED_MOVES_NOHIDE 7
2651
2652     static int started = STARTED_NONE;
2653     static char parse[20000];
2654     static int parse_pos = 0;
2655     static char buf[BUF_SIZE + 1];
2656     static int firstTime = TRUE, intfSet = FALSE;
2657     static ColorClass prevColor = ColorNormal;
2658     static int savingComment = FALSE;
2659     static int cmatch = 0; // continuation sequence match
2660     char *bp;
2661     char str[MSG_SIZ];
2662     int i, oldi;
2663     int buf_len;
2664     int next_out;
2665     int tkind;
2666     int backup;    /* [DM] For zippy color lines */
2667     char *p;
2668     char talker[MSG_SIZ]; // [HGM] chat
2669     int channel;
2670
2671     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2672
2673     if (appData.debugMode) {
2674       if (!error) {
2675         fprintf(debugFP, "<ICS: ");
2676         show_bytes(debugFP, data, count);
2677         fprintf(debugFP, "\n");
2678       }
2679     }
2680
2681     if (appData.debugMode) { int f = forwardMostMove;
2682         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2683                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2684                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2685     }
2686     if (count > 0) {
2687         /* If last read ended with a partial line that we couldn't parse,
2688            prepend it to the new read and try again. */
2689         if (leftover_len > 0) {
2690             for (i=0; i<leftover_len; i++)
2691               buf[i] = buf[leftover_start + i];
2692         }
2693
2694     /* copy new characters into the buffer */
2695     bp = buf + leftover_len;
2696     buf_len=leftover_len;
2697     for (i=0; i<count; i++)
2698     {
2699         // ignore these
2700         if (data[i] == '\r')
2701             continue;
2702
2703         // join lines split by ICS?
2704         if (!appData.noJoin)
2705         {
2706             /*
2707                 Joining just consists of finding matches against the
2708                 continuation sequence, and discarding that sequence
2709                 if found instead of copying it.  So, until a match
2710                 fails, there's nothing to do since it might be the
2711                 complete sequence, and thus, something we don't want
2712                 copied.
2713             */
2714             if (data[i] == cont_seq[cmatch])
2715             {
2716                 cmatch++;
2717                 if (cmatch == strlen(cont_seq))
2718                 {
2719                     cmatch = 0; // complete match.  just reset the counter
2720
2721                     /*
2722                         it's possible for the ICS to not include the space
2723                         at the end of the last word, making our [correct]
2724                         join operation fuse two separate words.  the server
2725                         does this when the space occurs at the width setting.
2726                     */
2727                     if (!buf_len || buf[buf_len-1] != ' ')
2728                     {
2729                         *bp++ = ' ';
2730                         buf_len++;
2731                     }
2732                 }
2733                 continue;
2734             }
2735             else if (cmatch)
2736             {
2737                 /*
2738                     match failed, so we have to copy what matched before
2739                     falling through and copying this character.  In reality,
2740                     this will only ever be just the newline character, but
2741                     it doesn't hurt to be precise.
2742                 */
2743                 strncpy(bp, cont_seq, cmatch);
2744                 bp += cmatch;
2745                 buf_len += cmatch;
2746                 cmatch = 0;
2747             }
2748         }
2749
2750         // copy this char
2751         *bp++ = data[i];
2752         buf_len++;
2753     }
2754
2755         buf[buf_len] = NULLCHAR;
2756 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2757         next_out = 0;
2758         leftover_start = 0;
2759
2760         i = 0;
2761         while (i < buf_len) {
2762             /* Deal with part of the TELNET option negotiation
2763                protocol.  We refuse to do anything beyond the
2764                defaults, except that we allow the WILL ECHO option,
2765                which ICS uses to turn off password echoing when we are
2766                directly connected to it.  We reject this option
2767                if localLineEditing mode is on (always on in xboard)
2768                and we are talking to port 23, which might be a real
2769                telnet server that will try to keep WILL ECHO on permanently.
2770              */
2771             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2772                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2773                 unsigned char option;
2774                 oldi = i;
2775                 switch ((unsigned char) buf[++i]) {
2776                   case TN_WILL:
2777                     if (appData.debugMode)
2778                       fprintf(debugFP, "\n<WILL ");
2779                     switch (option = (unsigned char) buf[++i]) {
2780                       case TN_ECHO:
2781                         if (appData.debugMode)
2782                           fprintf(debugFP, "ECHO ");
2783                         /* Reply only if this is a change, according
2784                            to the protocol rules. */
2785                         if (remoteEchoOption) break;
2786                         if (appData.localLineEditing &&
2787                             atoi(appData.icsPort) == TN_PORT) {
2788                             TelnetRequest(TN_DONT, TN_ECHO);
2789                         } else {
2790                             EchoOff();
2791                             TelnetRequest(TN_DO, TN_ECHO);
2792                             remoteEchoOption = TRUE;
2793                         }
2794                         break;
2795                       default:
2796                         if (appData.debugMode)
2797                           fprintf(debugFP, "%d ", option);
2798                         /* Whatever this is, we don't want it. */
2799                         TelnetRequest(TN_DONT, option);
2800                         break;
2801                     }
2802                     break;
2803                   case TN_WONT:
2804                     if (appData.debugMode)
2805                       fprintf(debugFP, "\n<WONT ");
2806                     switch (option = (unsigned char) buf[++i]) {
2807                       case TN_ECHO:
2808                         if (appData.debugMode)
2809                           fprintf(debugFP, "ECHO ");
2810                         /* Reply only if this is a change, according
2811                            to the protocol rules. */
2812                         if (!remoteEchoOption) break;
2813                         EchoOn();
2814                         TelnetRequest(TN_DONT, TN_ECHO);
2815                         remoteEchoOption = FALSE;
2816                         break;
2817                       default:
2818                         if (appData.debugMode)
2819                           fprintf(debugFP, "%d ", (unsigned char) option);
2820                         /* Whatever this is, it must already be turned
2821                            off, because we never agree to turn on
2822                            anything non-default, so according to the
2823                            protocol rules, we don't reply. */
2824                         break;
2825                     }
2826                     break;
2827                   case TN_DO:
2828                     if (appData.debugMode)
2829                       fprintf(debugFP, "\n<DO ");
2830                     switch (option = (unsigned char) buf[++i]) {
2831                       default:
2832                         /* Whatever this is, we refuse to do it. */
2833                         if (appData.debugMode)
2834                           fprintf(debugFP, "%d ", option);
2835                         TelnetRequest(TN_WONT, option);
2836                         break;
2837                     }
2838                     break;
2839                   case TN_DONT:
2840                     if (appData.debugMode)
2841                       fprintf(debugFP, "\n<DONT ");
2842                     switch (option = (unsigned char) buf[++i]) {
2843                       default:
2844                         if (appData.debugMode)
2845                           fprintf(debugFP, "%d ", option);
2846                         /* Whatever this is, we are already not doing
2847                            it, because we never agree to do anything
2848                            non-default, so according to the protocol
2849                            rules, we don't reply. */
2850                         break;
2851                     }
2852                     break;
2853                   case TN_IAC:
2854                     if (appData.debugMode)
2855                       fprintf(debugFP, "\n<IAC ");
2856                     /* Doubled IAC; pass it through */
2857                     i--;
2858                     break;
2859                   default:
2860                     if (appData.debugMode)
2861                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2862                     /* Drop all other telnet commands on the floor */
2863                     break;
2864                 }
2865                 if (oldi > next_out)
2866                   SendToPlayer(&buf[next_out], oldi - next_out);
2867                 if (++i > next_out)
2868                   next_out = i;
2869                 continue;
2870             }
2871
2872             /* OK, this at least will *usually* work */
2873             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2874                 loggedOn = TRUE;
2875             }
2876
2877             if (loggedOn && !intfSet) {
2878                 if (ics_type == ICS_ICC) {
2879                   snprintf(str, MSG_SIZ,
2880                           "/set-quietly interface %s\n/set-quietly style 12\n",
2881                           programVersion);
2882                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2883                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2884                 } else if (ics_type == ICS_CHESSNET) {
2885                   snprintf(str, MSG_SIZ, "/style 12\n");
2886                 } else {
2887                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2888                   strcat(str, programVersion);
2889                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2890                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2891                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2892 #ifdef WIN32
2893                   strcat(str, "$iset nohighlight 1\n");
2894 #endif
2895                   strcat(str, "$iset lock 1\n$style 12\n");
2896                 }
2897                 SendToICS(str);
2898                 NotifyFrontendLogin();
2899                 intfSet = TRUE;
2900             }
2901
2902             if (started == STARTED_COMMENT) {
2903                 /* Accumulate characters in comment */
2904                 parse[parse_pos++] = buf[i];
2905                 if (buf[i] == '\n') {
2906                     parse[parse_pos] = NULLCHAR;
2907                     if(chattingPartner>=0) {
2908                         char mess[MSG_SIZ];
2909                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2910                         OutputChatMessage(chattingPartner, mess);
2911                         chattingPartner = -1;
2912                         next_out = i+1; // [HGM] suppress printing in ICS window
2913                     } else
2914                     if(!suppressKibitz) // [HGM] kibitz
2915                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2916                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2917                         int nrDigit = 0, nrAlph = 0, j;
2918                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2919                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2920                         parse[parse_pos] = NULLCHAR;
2921                         // try to be smart: if it does not look like search info, it should go to
2922                         // ICS interaction window after all, not to engine-output window.
2923                         for(j=0; j<parse_pos; j++) { // count letters and digits
2924                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2925                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2926                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2927                         }
2928                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2929                             int depth=0; float score;
2930                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2931                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2932                                 pvInfoList[forwardMostMove-1].depth = depth;
2933                                 pvInfoList[forwardMostMove-1].score = 100*score;
2934                             }
2935                             OutputKibitz(suppressKibitz, parse);
2936                         } else {
2937                             char tmp[MSG_SIZ];
2938                             if(gameMode == IcsObserving) // restore original ICS messages
2939                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2940                             else
2941                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2942                             SendToPlayer(tmp, strlen(tmp));
2943                         }
2944                         next_out = i+1; // [HGM] suppress printing in ICS window
2945                     }
2946                     started = STARTED_NONE;
2947                 } else {
2948                     /* Don't match patterns against characters in comment */
2949                     i++;
2950                     continue;
2951                 }
2952             }
2953             if (started == STARTED_CHATTER) {
2954                 if (buf[i] != '\n') {
2955                     /* Don't match patterns against characters in chatter */
2956                     i++;
2957                     continue;
2958                 }
2959                 started = STARTED_NONE;
2960                 if(suppressKibitz) next_out = i+1;
2961             }
2962
2963             /* Kludge to deal with rcmd protocol */
2964             if (firstTime && looking_at(buf, &i, "\001*")) {
2965                 DisplayFatalError(&buf[1], 0, 1);
2966                 continue;
2967             } else {
2968                 firstTime = FALSE;
2969             }
2970
2971             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2972                 ics_type = ICS_ICC;
2973                 ics_prefix = "/";
2974                 if (appData.debugMode)
2975                   fprintf(debugFP, "ics_type %d\n", ics_type);
2976                 continue;
2977             }
2978             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2979                 ics_type = ICS_FICS;
2980                 ics_prefix = "$";
2981                 if (appData.debugMode)
2982                   fprintf(debugFP, "ics_type %d\n", ics_type);
2983                 continue;
2984             }
2985             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2986                 ics_type = ICS_CHESSNET;
2987                 ics_prefix = "/";
2988                 if (appData.debugMode)
2989                   fprintf(debugFP, "ics_type %d\n", ics_type);
2990                 continue;
2991             }
2992
2993             if (!loggedOn &&
2994                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2995                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2996                  looking_at(buf, &i, "will be \"*\""))) {
2997               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2998               continue;
2999             }
3000
3001             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3002               char buf[MSG_SIZ];
3003               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3004               DisplayIcsInteractionTitle(buf);
3005               have_set_title = TRUE;
3006             }
3007
3008             /* skip finger notes */
3009             if (started == STARTED_NONE &&
3010                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3011                  (buf[i] == '1' && buf[i+1] == '0')) &&
3012                 buf[i+2] == ':' && buf[i+3] == ' ') {
3013               started = STARTED_CHATTER;
3014               i += 3;
3015               continue;
3016             }
3017
3018             oldi = i;
3019             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3020             if(appData.seekGraph) {
3021                 if(soughtPending && MatchSoughtLine(buf+i)) {
3022                     i = strstr(buf+i, "rated") - buf;
3023                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3024                     next_out = leftover_start = i;
3025                     started = STARTED_CHATTER;
3026                     suppressKibitz = TRUE;
3027                     continue;
3028                 }
3029                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3030                         && looking_at(buf, &i, "* ads displayed")) {
3031                     soughtPending = FALSE;
3032                     seekGraphUp = TRUE;
3033                     DrawSeekGraph();
3034                     continue;
3035                 }
3036                 if(appData.autoRefresh) {
3037                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3038                         int s = (ics_type == ICS_ICC); // ICC format differs
3039                         if(seekGraphUp)
3040                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3041                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3042                         looking_at(buf, &i, "*% "); // eat prompt
3043                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3044                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3045                         next_out = i; // suppress
3046                         continue;
3047                     }
3048                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3049                         char *p = star_match[0];
3050                         while(*p) {
3051                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3052                             while(*p && *p++ != ' '); // next
3053                         }
3054                         looking_at(buf, &i, "*% "); // eat prompt
3055                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3056                         next_out = i;
3057                         continue;
3058                     }
3059                 }
3060             }
3061
3062             /* skip formula vars */
3063             if (started == STARTED_NONE &&
3064                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3065               started = STARTED_CHATTER;
3066               i += 3;
3067               continue;
3068             }
3069
3070             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3071             if (appData.autoKibitz && started == STARTED_NONE &&
3072                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3073                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3074                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3075                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3076                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3077                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3078                         suppressKibitz = TRUE;
3079                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3080                         next_out = i;
3081                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3082                                 && (gameMode == IcsPlayingWhite)) ||
3083                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3084                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3085                             started = STARTED_CHATTER; // own kibitz we simply discard
3086                         else {
3087                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3088                             parse_pos = 0; parse[0] = NULLCHAR;
3089                             savingComment = TRUE;
3090                             suppressKibitz = gameMode != IcsObserving ? 2 :
3091                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3092                         }
3093                         continue;
3094                 } else
3095                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3096                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3097                          && atoi(star_match[0])) {
3098                     // suppress the acknowledgements of our own autoKibitz
3099                     char *p;
3100                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3101                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3102                     SendToPlayer(star_match[0], strlen(star_match[0]));
3103                     if(looking_at(buf, &i, "*% ")) // eat prompt
3104                         suppressKibitz = FALSE;
3105                     next_out = i;
3106                     continue;
3107                 }
3108             } // [HGM] kibitz: end of patch
3109
3110             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3111
3112             // [HGM] chat: intercept tells by users for which we have an open chat window
3113             channel = -1;
3114             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3115                                            looking_at(buf, &i, "* whispers:") ||
3116                                            looking_at(buf, &i, "* kibitzes:") ||
3117                                            looking_at(buf, &i, "* shouts:") ||
3118                                            looking_at(buf, &i, "* c-shouts:") ||
3119                                            looking_at(buf, &i, "--> * ") ||
3120                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3121                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3122                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3123                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3124                 int p;
3125                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3126                 chattingPartner = -1;
3127
3128                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3129                 for(p=0; p<MAX_CHAT; p++) {
3130                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3131                     talker[0] = '['; strcat(talker, "] ");
3132                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3133                     chattingPartner = p; break;
3134                     }
3135                 } else
3136                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3137                 for(p=0; p<MAX_CHAT; p++) {
3138                     if(!strcmp("kibitzes", chatPartner[p])) {
3139                         talker[0] = '['; strcat(talker, "] ");
3140                         chattingPartner = p; break;
3141                     }
3142                 } else
3143                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3144                 for(p=0; p<MAX_CHAT; p++) {
3145                     if(!strcmp("whispers", chatPartner[p])) {
3146                         talker[0] = '['; strcat(talker, "] ");
3147                         chattingPartner = p; break;
3148                     }
3149                 } else
3150                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3151                   if(buf[i-8] == '-' && buf[i-3] == 't')
3152                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3153                     if(!strcmp("c-shouts", chatPartner[p])) {
3154                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3155                         chattingPartner = p; break;
3156                     }
3157                   }
3158                   if(chattingPartner < 0)
3159                   for(p=0; p<MAX_CHAT; p++) {
3160                     if(!strcmp("shouts", chatPartner[p])) {
3161                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3162                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3163                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3164                         chattingPartner = p; break;
3165                     }
3166                   }
3167                 }
3168                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3169                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3170                     talker[0] = 0; Colorize(ColorTell, FALSE);
3171                     chattingPartner = p; break;
3172                 }
3173                 if(chattingPartner<0) i = oldi; else {
3174                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3175                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3176                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3177                     started = STARTED_COMMENT;
3178                     parse_pos = 0; parse[0] = NULLCHAR;
3179                     savingComment = 3 + chattingPartner; // counts as TRUE
3180                     suppressKibitz = TRUE;
3181                     continue;
3182                 }
3183             } // [HGM] chat: end of patch
3184
3185           backup = i;
3186             if (appData.zippyTalk || appData.zippyPlay) {
3187                 /* [DM] Backup address for color zippy lines */
3188 #if ZIPPY
3189                if (loggedOn == TRUE)
3190                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3191                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3192 #endif
3193             } // [DM] 'else { ' deleted
3194                 if (
3195                     /* Regular tells and says */
3196                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3197                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3198                     looking_at(buf, &i, "* says: ") ||
3199                     /* Don't color "message" or "messages" output */
3200                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3201                     looking_at(buf, &i, "*. * at *:*: ") ||
3202                     looking_at(buf, &i, "--* (*:*): ") ||
3203                     /* Message notifications (same color as tells) */
3204                     looking_at(buf, &i, "* has left a message ") ||
3205                     looking_at(buf, &i, "* just sent you a message:\n") ||
3206                     /* Whispers and kibitzes */
3207                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3208                     looking_at(buf, &i, "* kibitzes: ") ||
3209                     /* Channel tells */
3210                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3211
3212                   if (tkind == 1 && strchr(star_match[0], ':')) {
3213                       /* Avoid "tells you:" spoofs in channels */
3214                      tkind = 3;
3215                   }
3216                   if (star_match[0][0] == NULLCHAR ||
3217                       strchr(star_match[0], ' ') ||
3218                       (tkind == 3 && strchr(star_match[1], ' '))) {
3219                     /* Reject bogus matches */
3220                     i = oldi;
3221                   } else {
3222                     if (appData.colorize) {
3223                       if (oldi > next_out) {
3224                         SendToPlayer(&buf[next_out], oldi - next_out);
3225                         next_out = oldi;
3226                       }
3227                       switch (tkind) {
3228                       case 1:
3229                         Colorize(ColorTell, FALSE);
3230                         curColor = ColorTell;
3231                         break;
3232                       case 2:
3233                         Colorize(ColorKibitz, FALSE);
3234                         curColor = ColorKibitz;
3235                         break;
3236                       case 3:
3237                         p = strrchr(star_match[1], '(');
3238                         if (p == NULL) {
3239                           p = star_match[1];
3240                         } else {
3241                           p++;
3242                         }
3243                         if (atoi(p) == 1) {
3244                           Colorize(ColorChannel1, FALSE);
3245                           curColor = ColorChannel1;
3246                         } else {
3247                           Colorize(ColorChannel, FALSE);
3248                           curColor = ColorChannel;
3249                         }
3250                         break;
3251                       case 5:
3252                         curColor = ColorNormal;
3253                         break;
3254                       }
3255                     }
3256                     if (started == STARTED_NONE && appData.autoComment &&
3257                         (gameMode == IcsObserving ||
3258                          gameMode == IcsPlayingWhite ||
3259                          gameMode == IcsPlayingBlack)) {
3260                       parse_pos = i - oldi;
3261                       memcpy(parse, &buf[oldi], parse_pos);
3262                       parse[parse_pos] = NULLCHAR;
3263                       started = STARTED_COMMENT;
3264                       savingComment = TRUE;
3265                     } else {
3266                       started = STARTED_CHATTER;
3267                       savingComment = FALSE;
3268                     }
3269                     loggedOn = TRUE;
3270                     continue;
3271                   }
3272                 }
3273
3274                 if (looking_at(buf, &i, "* s-shouts: ") ||
3275                     looking_at(buf, &i, "* c-shouts: ")) {
3276                     if (appData.colorize) {
3277                         if (oldi > next_out) {
3278                             SendToPlayer(&buf[next_out], oldi - next_out);
3279                             next_out = oldi;
3280                         }
3281                         Colorize(ColorSShout, FALSE);
3282                         curColor = ColorSShout;
3283                     }
3284                     loggedOn = TRUE;
3285                     started = STARTED_CHATTER;
3286                     continue;
3287                 }
3288
3289                 if (looking_at(buf, &i, "--->")) {
3290                     loggedOn = TRUE;
3291                     continue;
3292                 }
3293
3294                 if (looking_at(buf, &i, "* shouts: ") ||
3295                     looking_at(buf, &i, "--> ")) {
3296                     if (appData.colorize) {
3297                         if (oldi > next_out) {
3298                             SendToPlayer(&buf[next_out], oldi - next_out);
3299                             next_out = oldi;
3300                         }
3301                         Colorize(ColorShout, FALSE);
3302                         curColor = ColorShout;
3303                     }
3304                     loggedOn = TRUE;
3305                     started = STARTED_CHATTER;
3306                     continue;
3307                 }
3308
3309                 if (looking_at( buf, &i, "Challenge:")) {
3310                     if (appData.colorize) {
3311                         if (oldi > next_out) {
3312                             SendToPlayer(&buf[next_out], oldi - next_out);
3313                             next_out = oldi;
3314                         }
3315                         Colorize(ColorChallenge, FALSE);
3316                         curColor = ColorChallenge;
3317                     }
3318                     loggedOn = TRUE;
3319                     continue;
3320                 }
3321
3322                 if (looking_at(buf, &i, "* offers you") ||
3323                     looking_at(buf, &i, "* offers to be") ||
3324                     looking_at(buf, &i, "* would like to") ||
3325                     looking_at(buf, &i, "* requests to") ||
3326                     looking_at(buf, &i, "Your opponent offers") ||
3327                     looking_at(buf, &i, "Your opponent requests")) {
3328
3329                     if (appData.colorize) {
3330                         if (oldi > next_out) {
3331                             SendToPlayer(&buf[next_out], oldi - next_out);
3332                             next_out = oldi;
3333                         }
3334                         Colorize(ColorRequest, FALSE);
3335                         curColor = ColorRequest;
3336                     }
3337                     continue;
3338                 }
3339
3340                 if (looking_at(buf, &i, "* (*) seeking")) {
3341                     if (appData.colorize) {
3342                         if (oldi > next_out) {
3343                             SendToPlayer(&buf[next_out], oldi - next_out);
3344                             next_out = oldi;
3345                         }
3346                         Colorize(ColorSeek, FALSE);
3347                         curColor = ColorSeek;
3348                     }
3349                     continue;
3350             }
3351
3352           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3353
3354             if (looking_at(buf, &i, "\\   ")) {
3355                 if (prevColor != ColorNormal) {
3356                     if (oldi > next_out) {
3357                         SendToPlayer(&buf[next_out], oldi - next_out);
3358                         next_out = oldi;
3359                     }
3360                     Colorize(prevColor, TRUE);
3361                     curColor = prevColor;
3362                 }
3363                 if (savingComment) {
3364                     parse_pos = i - oldi;
3365                     memcpy(parse, &buf[oldi], parse_pos);
3366                     parse[parse_pos] = NULLCHAR;
3367                     started = STARTED_COMMENT;
3368                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3369                         chattingPartner = savingComment - 3; // kludge to remember the box
3370                 } else {
3371                     started = STARTED_CHATTER;
3372                 }
3373                 continue;
3374             }
3375
3376             if (looking_at(buf, &i, "Black Strength :") ||
3377                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3378                 looking_at(buf, &i, "<10>") ||
3379                 looking_at(buf, &i, "#@#")) {
3380                 /* Wrong board style */
3381                 loggedOn = TRUE;
3382                 SendToICS(ics_prefix);
3383                 SendToICS("set style 12\n");
3384                 SendToICS(ics_prefix);
3385                 SendToICS("refresh\n");
3386                 continue;
3387             }
3388
3389             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3390                 ICSInitScript();
3391                 have_sent_ICS_logon = 1;
3392                 continue;
3393             }
3394
3395             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3396                 (looking_at(buf, &i, "\n<12> ") ||
3397                  looking_at(buf, &i, "<12> "))) {
3398                 loggedOn = TRUE;
3399                 if (oldi > next_out) {
3400                     SendToPlayer(&buf[next_out], oldi - next_out);
3401                 }
3402                 next_out = i;
3403                 started = STARTED_BOARD;
3404                 parse_pos = 0;
3405                 continue;
3406             }
3407
3408             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3409                 looking_at(buf, &i, "<b1> ")) {
3410                 if (oldi > next_out) {
3411                     SendToPlayer(&buf[next_out], oldi - next_out);
3412                 }
3413                 next_out = i;
3414                 started = STARTED_HOLDINGS;
3415                 parse_pos = 0;
3416                 continue;
3417             }
3418
3419             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3420                 loggedOn = TRUE;
3421                 /* Header for a move list -- first line */
3422
3423                 switch (ics_getting_history) {
3424                   case H_FALSE:
3425                     switch (gameMode) {
3426                       case IcsIdle:
3427                       case BeginningOfGame:
3428                         /* User typed "moves" or "oldmoves" while we
3429                            were idle.  Pretend we asked for these
3430                            moves and soak them up so user can step
3431                            through them and/or save them.
3432                            */
3433                         Reset(FALSE, TRUE);
3434                         gameMode = IcsObserving;
3435                         ModeHighlight();
3436                         ics_gamenum = -1;
3437                         ics_getting_history = H_GOT_UNREQ_HEADER;
3438                         break;
3439                       case EditGame: /*?*/
3440                       case EditPosition: /*?*/
3441                         /* Should above feature work in these modes too? */
3442                         /* For now it doesn't */
3443                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3444                         break;
3445                       default:
3446                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3447                         break;
3448                     }
3449                     break;
3450                   case H_REQUESTED:
3451                     /* Is this the right one? */
3452                     if (gameInfo.white && gameInfo.black &&
3453                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3454                         strcmp(gameInfo.black, star_match[2]) == 0) {
3455                         /* All is well */
3456                         ics_getting_history = H_GOT_REQ_HEADER;
3457                     }
3458                     break;
3459                   case H_GOT_REQ_HEADER:
3460                   case H_GOT_UNREQ_HEADER:
3461                   case H_GOT_UNWANTED_HEADER:
3462                   case H_GETTING_MOVES:
3463                     /* Should not happen */
3464                     DisplayError(_("Error gathering move list: two headers"), 0);
3465                     ics_getting_history = H_FALSE;
3466                     break;
3467                 }
3468
3469                 /* Save player ratings into gameInfo if needed */
3470                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3471                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3472                     (gameInfo.whiteRating == -1 ||
3473                      gameInfo.blackRating == -1)) {
3474
3475                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3476                     gameInfo.blackRating = string_to_rating(star_match[3]);
3477                     if (appData.debugMode)
3478                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3479                               gameInfo.whiteRating, gameInfo.blackRating);
3480                 }
3481                 continue;
3482             }
3483
3484             if (looking_at(buf, &i,
3485               "* * match, initial time: * minute*, increment: * second")) {
3486                 /* Header for a move list -- second line */
3487                 /* Initial board will follow if this is a wild game */
3488                 if (gameInfo.event != NULL) free(gameInfo.event);
3489                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3490                 gameInfo.event = StrSave(str);
3491                 /* [HGM] we switched variant. Translate boards if needed. */
3492                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3493                 continue;
3494             }
3495
3496             if (looking_at(buf, &i, "Move  ")) {
3497                 /* Beginning of a move list */
3498                 switch (ics_getting_history) {
3499                   case H_FALSE:
3500                     /* Normally should not happen */
3501                     /* Maybe user hit reset while we were parsing */
3502                     break;
3503                   case H_REQUESTED:
3504                     /* Happens if we are ignoring a move list that is not
3505                      * the one we just requested.  Common if the user
3506                      * tries to observe two games without turning off
3507                      * getMoveList */
3508                     break;
3509                   case H_GETTING_MOVES:
3510                     /* Should not happen */
3511                     DisplayError(_("Error gathering move list: nested"), 0);
3512                     ics_getting_history = H_FALSE;
3513                     break;
3514                   case H_GOT_REQ_HEADER:
3515                     ics_getting_history = H_GETTING_MOVES;
3516                     started = STARTED_MOVES;
3517                     parse_pos = 0;
3518                     if (oldi > next_out) {
3519                         SendToPlayer(&buf[next_out], oldi - next_out);
3520                     }
3521                     break;
3522                   case H_GOT_UNREQ_HEADER:
3523                     ics_getting_history = H_GETTING_MOVES;
3524                     started = STARTED_MOVES_NOHIDE;
3525                     parse_pos = 0;
3526                     break;
3527                   case H_GOT_UNWANTED_HEADER:
3528                     ics_getting_history = H_FALSE;
3529                     break;
3530                 }
3531                 continue;
3532             }
3533
3534             if (looking_at(buf, &i, "% ") ||
3535                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3536                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3537                 if(soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3538                     soughtPending = FALSE;
3539                     seekGraphUp = TRUE;
3540                     DrawSeekGraph();
3541                 }
3542                 if(suppressKibitz) next_out = i;
3543                 savingComment = FALSE;
3544                 suppressKibitz = 0;
3545                 switch (started) {
3546                   case STARTED_MOVES:
3547                   case STARTED_MOVES_NOHIDE:
3548                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3549                     parse[parse_pos + i - oldi] = NULLCHAR;
3550                     ParseGameHistory(parse);
3551 #if ZIPPY
3552                     if (appData.zippyPlay && first.initDone) {
3553                         FeedMovesToProgram(&first, forwardMostMove);
3554                         if (gameMode == IcsPlayingWhite) {
3555                             if (WhiteOnMove(forwardMostMove)) {
3556                                 if (first.sendTime) {
3557                                   if (first.useColors) {
3558                                     SendToProgram("black\n", &first);
3559                                   }
3560                                   SendTimeRemaining(&first, TRUE);
3561                                 }
3562                                 if (first.useColors) {
3563                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3564                                 }
3565                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3566                                 first.maybeThinking = TRUE;
3567                             } else {
3568                                 if (first.usePlayother) {
3569                                   if (first.sendTime) {
3570                                     SendTimeRemaining(&first, TRUE);
3571                                   }
3572                                   SendToProgram("playother\n", &first);
3573                                   firstMove = FALSE;
3574                                 } else {
3575                                   firstMove = TRUE;
3576                                 }
3577                             }
3578                         } else if (gameMode == IcsPlayingBlack) {
3579                             if (!WhiteOnMove(forwardMostMove)) {
3580                                 if (first.sendTime) {
3581                                   if (first.useColors) {
3582                                     SendToProgram("white\n", &first);
3583                                   }
3584                                   SendTimeRemaining(&first, FALSE);
3585                                 }
3586                                 if (first.useColors) {
3587                                   SendToProgram("black\n", &first);
3588                                 }
3589                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3590                                 first.maybeThinking = TRUE;
3591                             } else {
3592                                 if (first.usePlayother) {
3593                                   if (first.sendTime) {
3594                                     SendTimeRemaining(&first, FALSE);
3595                                   }
3596                                   SendToProgram("playother\n", &first);
3597                                   firstMove = FALSE;
3598                                 } else {
3599                                   firstMove = TRUE;
3600                                 }
3601                             }
3602                         }
3603                     }
3604 #endif
3605                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3606                         /* Moves came from oldmoves or moves command
3607                            while we weren't doing anything else.
3608                            */
3609                         currentMove = forwardMostMove;
3610                         ClearHighlights();/*!!could figure this out*/
3611                         flipView = appData.flipView;
3612                         DrawPosition(TRUE, boards[currentMove]);
3613                         DisplayBothClocks();
3614                         snprintf(str, MSG_SIZ, "%s %s %s",
3615                                 gameInfo.white, _("vs."),  gameInfo.black);
3616                         DisplayTitle(str);
3617                         gameMode = IcsIdle;
3618                     } else {
3619                         /* Moves were history of an active game */
3620                         if (gameInfo.resultDetails != NULL) {
3621                             free(gameInfo.resultDetails);
3622                             gameInfo.resultDetails = NULL;
3623                         }
3624                     }
3625                     HistorySet(parseList, backwardMostMove,
3626                                forwardMostMove, currentMove-1);
3627                     DisplayMove(currentMove - 1);
3628                     if (started == STARTED_MOVES) next_out = i;
3629                     started = STARTED_NONE;
3630                     ics_getting_history = H_FALSE;
3631                     break;
3632
3633                   case STARTED_OBSERVE:
3634                     started = STARTED_NONE;
3635                     SendToICS(ics_prefix);
3636                     SendToICS("refresh\n");
3637                     break;
3638
3639                   default:
3640                     break;
3641                 }
3642                 if(bookHit) { // [HGM] book: simulate book reply
3643                     static char bookMove[MSG_SIZ]; // a bit generous?
3644
3645                     programStats.nodes = programStats.depth = programStats.time =
3646                     programStats.score = programStats.got_only_move = 0;
3647                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3648
3649                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3650                     strcat(bookMove, bookHit);
3651                     HandleMachineMove(bookMove, &first);
3652                 }
3653                 continue;
3654             }
3655
3656             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3657                  started == STARTED_HOLDINGS ||
3658                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3659                 /* Accumulate characters in move list or board */
3660                 parse[parse_pos++] = buf[i];
3661             }
3662
3663             /* Start of game messages.  Mostly we detect start of game
3664                when the first board image arrives.  On some versions
3665                of the ICS, though, we need to do a "refresh" after starting
3666                to observe in order to get the current board right away. */
3667             if (looking_at(buf, &i, "Adding game * to observation list")) {
3668                 started = STARTED_OBSERVE;
3669                 continue;
3670             }
3671
3672             /* Handle auto-observe */
3673             if (appData.autoObserve &&
3674                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3675                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3676                 char *player;
3677                 /* Choose the player that was highlighted, if any. */
3678                 if (star_match[0][0] == '\033' ||
3679                     star_match[1][0] != '\033') {
3680                     player = star_match[0];
3681                 } else {
3682                     player = star_match[2];
3683                 }
3684                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3685                         ics_prefix, StripHighlightAndTitle(player));
3686                 SendToICS(str);
3687
3688                 /* Save ratings from notify string */
3689                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3690                 player1Rating = string_to_rating(star_match[1]);
3691                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3692                 player2Rating = string_to_rating(star_match[3]);
3693
3694                 if (appData.debugMode)
3695                   fprintf(debugFP,
3696                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3697                           player1Name, player1Rating,
3698                           player2Name, player2Rating);
3699
3700                 continue;
3701             }
3702
3703             /* Deal with automatic examine mode after a game,
3704                and with IcsObserving -> IcsExamining transition */
3705             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3706                 looking_at(buf, &i, "has made you an examiner of game *")) {
3707
3708                 int gamenum = atoi(star_match[0]);
3709                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3710                     gamenum == ics_gamenum) {
3711                     /* We were already playing or observing this game;
3712                        no need to refetch history */
3713                     gameMode = IcsExamining;
3714                     if (pausing) {
3715                         pauseExamForwardMostMove = forwardMostMove;
3716                     } else if (currentMove < forwardMostMove) {
3717                         ForwardInner(forwardMostMove);
3718                     }
3719                 } else {
3720                     /* I don't think this case really can happen */
3721                     SendToICS(ics_prefix);
3722                     SendToICS("refresh\n");
3723                 }
3724                 continue;
3725             }
3726
3727             /* Error messages */
3728 //          if (ics_user_moved) {
3729             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3730                 if (looking_at(buf, &i, "Illegal move") ||
3731                     looking_at(buf, &i, "Not a legal move") ||
3732                     looking_at(buf, &i, "Your king is in check") ||
3733                     looking_at(buf, &i, "It isn't your turn") ||
3734                     looking_at(buf, &i, "It is not your move")) {
3735                     /* Illegal move */
3736                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3737                         currentMove = forwardMostMove-1;
3738                         DisplayMove(currentMove - 1); /* before DMError */
3739                         DrawPosition(FALSE, boards[currentMove]);
3740                         SwitchClocks(forwardMostMove-1); // [HGM] race
3741                         DisplayBothClocks();
3742                     }
3743                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3744                     ics_user_moved = 0;
3745                     continue;
3746                 }
3747             }
3748
3749             if (looking_at(buf, &i, "still have time") ||
3750                 looking_at(buf, &i, "not out of time") ||
3751                 looking_at(buf, &i, "either player is out of time") ||
3752                 looking_at(buf, &i, "has timeseal; checking")) {
3753                 /* We must have called his flag a little too soon */
3754                 whiteFlag = blackFlag = FALSE;
3755                 continue;
3756             }
3757
3758             if (looking_at(buf, &i, "added * seconds to") ||
3759                 looking_at(buf, &i, "seconds were added to")) {
3760                 /* Update the clocks */
3761                 SendToICS(ics_prefix);
3762                 SendToICS("refresh\n");
3763                 continue;
3764             }
3765
3766             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3767                 ics_clock_paused = TRUE;
3768                 StopClocks();
3769                 continue;
3770             }
3771
3772             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3773                 ics_clock_paused = FALSE;
3774                 StartClocks();
3775                 continue;
3776             }
3777
3778             /* Grab player ratings from the Creating: message.
3779                Note we have to check for the special case when
3780                the ICS inserts things like [white] or [black]. */
3781             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3782                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3783                 /* star_matches:
3784                    0    player 1 name (not necessarily white)
3785                    1    player 1 rating
3786                    2    empty, white, or black (IGNORED)
3787                    3    player 2 name (not necessarily black)
3788                    4    player 2 rating
3789
3790                    The names/ratings are sorted out when the game
3791                    actually starts (below).
3792                 */
3793                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3794                 player1Rating = string_to_rating(star_match[1]);
3795                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3796                 player2Rating = string_to_rating(star_match[4]);
3797
3798                 if (appData.debugMode)
3799                   fprintf(debugFP,
3800                           "Ratings from 'Creating:' %s %d, %s %d\n",
3801                           player1Name, player1Rating,
3802                           player2Name, player2Rating);
3803
3804                 continue;
3805             }
3806
3807             /* Improved generic start/end-of-game messages */
3808             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3809                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3810                 /* If tkind == 0: */
3811                 /* star_match[0] is the game number */
3812                 /*           [1] is the white player's name */
3813                 /*           [2] is the black player's name */
3814                 /* For end-of-game: */
3815                 /*           [3] is the reason for the game end */
3816                 /*           [4] is a PGN end game-token, preceded by " " */
3817                 /* For start-of-game: */
3818                 /*           [3] begins with "Creating" or "Continuing" */
3819                 /*           [4] is " *" or empty (don't care). */
3820                 int gamenum = atoi(star_match[0]);
3821                 char *whitename, *blackname, *why, *endtoken;
3822                 ChessMove endtype = EndOfFile;
3823
3824                 if (tkind == 0) {
3825                   whitename = star_match[1];
3826                   blackname = star_match[2];
3827                   why = star_match[3];
3828                   endtoken = star_match[4];
3829                 } else {
3830                   whitename = star_match[1];
3831                   blackname = star_match[3];
3832                   why = star_match[5];
3833                   endtoken = star_match[6];
3834                 }
3835
3836                 /* Game start messages */
3837                 if (strncmp(why, "Creating ", 9) == 0 ||
3838                     strncmp(why, "Continuing ", 11) == 0) {
3839                     gs_gamenum = gamenum;
3840                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3841                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3842                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3843 #if ZIPPY
3844                     if (appData.zippyPlay) {
3845                         ZippyGameStart(whitename, blackname);
3846                     }
3847 #endif /*ZIPPY*/
3848                     partnerBoardValid = FALSE; // [HGM] bughouse
3849                     continue;
3850                 }
3851
3852                 /* Game end messages */
3853                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3854                     ics_gamenum != gamenum) {
3855                     continue;
3856                 }
3857                 while (endtoken[0] == ' ') endtoken++;
3858                 switch (endtoken[0]) {
3859                   case '*':
3860                   default:
3861                     endtype = GameUnfinished;
3862                     break;
3863                   case '0':
3864                     endtype = BlackWins;
3865                     break;
3866                   case '1':
3867                     if (endtoken[1] == '/')
3868                       endtype = GameIsDrawn;
3869                     else
3870                       endtype = WhiteWins;
3871                     break;
3872                 }
3873                 GameEnds(endtype, why, GE_ICS);
3874 #if ZIPPY
3875                 if (appData.zippyPlay && first.initDone) {
3876                     ZippyGameEnd(endtype, why);
3877                     if (first.pr == NoProc) {
3878                       /* Start the next process early so that we'll
3879                          be ready for the next challenge */
3880                       StartChessProgram(&first);
3881                     }
3882                     /* Send "new" early, in case this command takes
3883                        a long time to finish, so that we'll be ready
3884                        for the next challenge. */
3885                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3886                     Reset(TRUE, TRUE);
3887                 }
3888 #endif /*ZIPPY*/
3889                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3890                 continue;
3891             }
3892
3893             if (looking_at(buf, &i, "Removing game * from observation") ||
3894                 looking_at(buf, &i, "no longer observing game *") ||
3895                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3896                 if (gameMode == IcsObserving &&
3897                     atoi(star_match[0]) == ics_gamenum)
3898                   {
3899                       /* icsEngineAnalyze */
3900                       if (appData.icsEngineAnalyze) {
3901                             ExitAnalyzeMode();
3902                             ModeHighlight();
3903                       }
3904                       StopClocks();
3905                       gameMode = IcsIdle;
3906                       ics_gamenum = -1;
3907                       ics_user_moved = FALSE;
3908                   }
3909                 continue;
3910             }
3911
3912             if (looking_at(buf, &i, "no longer examining game *")) {
3913                 if (gameMode == IcsExamining &&
3914                     atoi(star_match[0]) == ics_gamenum)
3915                   {
3916                       gameMode = IcsIdle;
3917                       ics_gamenum = -1;
3918                       ics_user_moved = FALSE;
3919                   }
3920                 continue;
3921             }
3922
3923             /* Advance leftover_start past any newlines we find,
3924                so only partial lines can get reparsed */
3925             if (looking_at(buf, &i, "\n")) {
3926                 prevColor = curColor;
3927                 if (curColor != ColorNormal) {
3928                     if (oldi > next_out) {
3929                         SendToPlayer(&buf[next_out], oldi - next_out);
3930                         next_out = oldi;
3931                     }
3932                     Colorize(ColorNormal, FALSE);
3933                     curColor = ColorNormal;
3934                 }
3935                 if (started == STARTED_BOARD) {
3936                     started = STARTED_NONE;
3937                     parse[parse_pos] = NULLCHAR;
3938                     ParseBoard12(parse);
3939                     ics_user_moved = 0;
3940
3941                     /* Send premove here */
3942                     if (appData.premove) {
3943                       char str[MSG_SIZ];
3944                       if (currentMove == 0 &&
3945                           gameMode == IcsPlayingWhite &&
3946                           appData.premoveWhite) {
3947                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3948                         if (appData.debugMode)
3949                           fprintf(debugFP, "Sending premove:\n");
3950                         SendToICS(str);
3951                       } else if (currentMove == 1 &&
3952                                  gameMode == IcsPlayingBlack &&
3953                                  appData.premoveBlack) {
3954                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3955                         if (appData.debugMode)
3956                           fprintf(debugFP, "Sending premove:\n");
3957                         SendToICS(str);
3958                       } else if (gotPremove) {
3959                         gotPremove = 0;
3960                         ClearPremoveHighlights();
3961                         if (appData.debugMode)
3962                           fprintf(debugFP, "Sending premove:\n");
3963                           UserMoveEvent(premoveFromX, premoveFromY,
3964                                         premoveToX, premoveToY,
3965                                         premovePromoChar);
3966                       }
3967                     }
3968
3969                     /* Usually suppress following prompt */
3970                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3971                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3972                         if (looking_at(buf, &i, "*% ")) {
3973                             savingComment = FALSE;
3974                             suppressKibitz = 0;
3975                         }
3976                     }
3977                     next_out = i;
3978                 } else if (started == STARTED_HOLDINGS) {
3979                     int gamenum;
3980                     char new_piece[MSG_SIZ];
3981                     started = STARTED_NONE;
3982                     parse[parse_pos] = NULLCHAR;
3983                     if (appData.debugMode)
3984                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3985                                                         parse, currentMove);
3986                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3987                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3988                         if (gameInfo.variant == VariantNormal) {
3989                           /* [HGM] We seem to switch variant during a game!
3990                            * Presumably no holdings were displayed, so we have
3991                            * to move the position two files to the right to
3992                            * create room for them!
3993                            */
3994                           VariantClass newVariant;
3995                           switch(gameInfo.boardWidth) { // base guess on board width
3996                                 case 9:  newVariant = VariantShogi; break;
3997                                 case 10: newVariant = VariantGreat; break;
3998                                 default: newVariant = VariantCrazyhouse; break;
3999                           }
4000                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4001                           /* Get a move list just to see the header, which
4002                              will tell us whether this is really bug or zh */
4003                           if (ics_getting_history == H_FALSE) {
4004                             ics_getting_history = H_REQUESTED;
4005                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4006                             SendToICS(str);
4007                           }
4008                         }
4009                         new_piece[0] = NULLCHAR;
4010                         sscanf(parse, "game %d white [%s black [%s <- %s",
4011                                &gamenum, white_holding, black_holding,
4012                                new_piece);
4013                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4014                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4015                         /* [HGM] copy holdings to board holdings area */
4016                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4017                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4018                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4019 #if ZIPPY
4020                         if (appData.zippyPlay && first.initDone) {
4021                             ZippyHoldings(white_holding, black_holding,
4022                                           new_piece);
4023                         }
4024 #endif /*ZIPPY*/
4025                         if (tinyLayout || smallLayout) {
4026                             char wh[16], bh[16];
4027                             PackHolding(wh, white_holding);
4028                             PackHolding(bh, black_holding);
4029                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4030                                     gameInfo.white, gameInfo.black);
4031                         } else {
4032                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4033                                     gameInfo.white, white_holding, _("vs."),
4034                                     gameInfo.black, black_holding);
4035                         }
4036                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4037                         DrawPosition(FALSE, boards[currentMove]);
4038                         DisplayTitle(str);
4039                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4040                         sscanf(parse, "game %d white [%s black [%s <- %s",
4041                                &gamenum, white_holding, black_holding,
4042                                new_piece);
4043                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4044                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4045                         /* [HGM] copy holdings to partner-board holdings area */
4046                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4047                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4048                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4049                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4050                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4051                       }
4052                     }
4053                     /* Suppress following prompt */
4054                     if (looking_at(buf, &i, "*% ")) {
4055                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4056                         savingComment = FALSE;
4057                         suppressKibitz = 0;
4058                     }
4059                     next_out = i;
4060                 }
4061                 continue;
4062             }
4063
4064             i++;                /* skip unparsed character and loop back */
4065         }
4066
4067         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4068 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4069 //          SendToPlayer(&buf[next_out], i - next_out);
4070             started != STARTED_HOLDINGS && leftover_start > next_out) {
4071             SendToPlayer(&buf[next_out], leftover_start - next_out);
4072             next_out = i;
4073         }
4074
4075         leftover_len = buf_len - leftover_start;
4076         /* if buffer ends with something we couldn't parse,
4077            reparse it after appending the next read */
4078
4079     } else if (count == 0) {
4080         RemoveInputSource(isr);
4081         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4082     } else {
4083         DisplayFatalError(_("Error reading from ICS"), error, 1);
4084     }
4085 }
4086
4087
4088 /* Board style 12 looks like this:
4089
4090    <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
4091
4092  * The "<12> " is stripped before it gets to this routine.  The two
4093  * trailing 0's (flip state and clock ticking) are later addition, and
4094  * some chess servers may not have them, or may have only the first.
4095  * Additional trailing fields may be added in the future.
4096  */
4097
4098 #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"
4099
4100 #define RELATION_OBSERVING_PLAYED    0
4101 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4102 #define RELATION_PLAYING_MYMOVE      1
4103 #define RELATION_PLAYING_NOTMYMOVE  -1
4104 #define RELATION_EXAMINING           2
4105 #define RELATION_ISOLATED_BOARD     -3
4106 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4107
4108 void
4109 ParseBoard12 (char *string)
4110 {
4111     GameMode newGameMode;
4112     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4113     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4114     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4115     char to_play, board_chars[200];
4116     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4117     char black[32], white[32];
4118     Board board;
4119     int prevMove = currentMove;
4120     int ticking = 2;
4121     ChessMove moveType;
4122     int fromX, fromY, toX, toY;
4123     char promoChar;
4124     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4125     char *bookHit = NULL; // [HGM] book
4126     Boolean weird = FALSE, reqFlag = FALSE;
4127
4128     fromX = fromY = toX = toY = -1;
4129
4130     newGame = FALSE;
4131
4132     if (appData.debugMode)
4133       fprintf(debugFP, _("Parsing board: %s\n"), string);
4134
4135     move_str[0] = NULLCHAR;
4136     elapsed_time[0] = NULLCHAR;
4137     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4138         int  i = 0, j;
4139         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4140             if(string[i] == ' ') { ranks++; files = 0; }
4141             else files++;
4142             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4143             i++;
4144         }
4145         for(j = 0; j <i; j++) board_chars[j] = string[j];
4146         board_chars[i] = '\0';
4147         string += i + 1;
4148     }
4149     n = sscanf(string, PATTERN, &to_play, &double_push,
4150                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4151                &gamenum, white, black, &relation, &basetime, &increment,
4152                &white_stren, &black_stren, &white_time, &black_time,
4153                &moveNum, str, elapsed_time, move_str, &ics_flip,
4154                &ticking);
4155
4156     if (n < 21) {
4157         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4158         DisplayError(str, 0);
4159         return;
4160     }
4161
4162     /* Convert the move number to internal form */
4163     moveNum = (moveNum - 1) * 2;
4164     if (to_play == 'B') moveNum++;
4165     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4166       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4167                         0, 1);
4168       return;
4169     }
4170
4171     switch (relation) {
4172       case RELATION_OBSERVING_PLAYED:
4173       case RELATION_OBSERVING_STATIC:
4174         if (gamenum == -1) {
4175             /* Old ICC buglet */
4176             relation = RELATION_OBSERVING_STATIC;
4177         }
4178         newGameMode = IcsObserving;
4179         break;
4180       case RELATION_PLAYING_MYMOVE:
4181       case RELATION_PLAYING_NOTMYMOVE:
4182         newGameMode =
4183           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4184             IcsPlayingWhite : IcsPlayingBlack;
4185         break;
4186       case RELATION_EXAMINING:
4187         newGameMode = IcsExamining;
4188         break;
4189       case RELATION_ISOLATED_BOARD:
4190       default:
4191         /* Just display this board.  If user was doing something else,
4192            we will forget about it until the next board comes. */
4193         newGameMode = IcsIdle;
4194         break;
4195       case RELATION_STARTING_POSITION:
4196         newGameMode = gameMode;
4197         break;
4198     }
4199
4200     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4201          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4202       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4203       char *toSqr;
4204       for (k = 0; k < ranks; k++) {
4205         for (j = 0; j < files; j++)
4206           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4207         if(gameInfo.holdingsWidth > 1) {
4208              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4209              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4210         }
4211       }
4212       CopyBoard(partnerBoard, board);
4213       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4214         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4215         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4216       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4217       if(toSqr = strchr(str, '-')) {
4218         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4219         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4220       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4221       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4222       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4223       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4224       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4225       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4226                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4227       DisplayMessage(partnerStatus, "");
4228         partnerBoardValid = TRUE;
4229       return;
4230     }
4231
4232     /* Modify behavior for initial board display on move listing
4233        of wild games.
4234        */
4235     switch (ics_getting_history) {
4236       case H_FALSE:
4237       case H_REQUESTED:
4238         break;
4239       case H_GOT_REQ_HEADER:
4240       case H_GOT_UNREQ_HEADER:
4241         /* This is the initial position of the current game */
4242         gamenum = ics_gamenum;
4243         moveNum = 0;            /* old ICS bug workaround */
4244         if (to_play == 'B') {
4245           startedFromSetupPosition = TRUE;
4246           blackPlaysFirst = TRUE;
4247           moveNum = 1;
4248           if (forwardMostMove == 0) forwardMostMove = 1;
4249           if (backwardMostMove == 0) backwardMostMove = 1;
4250           if (currentMove == 0) currentMove = 1;
4251         }
4252         newGameMode = gameMode;
4253         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4254         break;
4255       case H_GOT_UNWANTED_HEADER:
4256         /* This is an initial board that we don't want */
4257         return;
4258       case H_GETTING_MOVES:
4259         /* Should not happen */
4260         DisplayError(_("Error gathering move list: extra board"), 0);
4261         ics_getting_history = H_FALSE;
4262         return;
4263     }
4264
4265    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4266                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4267      /* [HGM] We seem to have switched variant unexpectedly
4268       * Try to guess new variant from board size
4269       */
4270           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4271           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4272           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4273           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4274           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4275           if(!weird) newVariant = VariantNormal;
4276           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4277           /* Get a move list just to see the header, which
4278              will tell us whether this is really bug or zh */
4279           if (ics_getting_history == H_FALSE) {
4280             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4281             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4282             SendToICS(str);
4283           }
4284     }
4285
4286     /* Take action if this is the first board of a new game, or of a
4287        different game than is currently being displayed.  */
4288     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4289         relation == RELATION_ISOLATED_BOARD) {
4290
4291         /* Forget the old game and get the history (if any) of the new one */
4292         if (gameMode != BeginningOfGame) {
4293           Reset(TRUE, TRUE);
4294         }
4295         newGame = TRUE;
4296         if (appData.autoRaiseBoard) BoardToTop();
4297         prevMove = -3;
4298         if (gamenum == -1) {
4299             newGameMode = IcsIdle;
4300         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4301                    appData.getMoveList && !reqFlag) {
4302             /* Need to get game history */
4303             ics_getting_history = H_REQUESTED;
4304             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4305             SendToICS(str);
4306         }
4307
4308         /* Initially flip the board to have black on the bottom if playing
4309            black or if the ICS flip flag is set, but let the user change
4310            it with the Flip View button. */
4311         flipView = appData.autoFlipView ?
4312           (newGameMode == IcsPlayingBlack) || ics_flip :
4313           appData.flipView;
4314
4315         /* Done with values from previous mode; copy in new ones */
4316         gameMode = newGameMode;
4317         ModeHighlight();
4318         ics_gamenum = gamenum;
4319         if (gamenum == gs_gamenum) {
4320             int klen = strlen(gs_kind);
4321             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4322             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4323             gameInfo.event = StrSave(str);
4324         } else {
4325             gameInfo.event = StrSave("ICS game");
4326         }
4327         gameInfo.site = StrSave(appData.icsHost);
4328         gameInfo.date = PGNDate();
4329         gameInfo.round = StrSave("-");
4330         gameInfo.white = StrSave(white);
4331         gameInfo.black = StrSave(black);
4332         timeControl = basetime * 60 * 1000;
4333         timeControl_2 = 0;
4334         timeIncrement = increment * 1000;
4335         movesPerSession = 0;
4336         gameInfo.timeControl = TimeControlTagValue();
4337         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4338   if (appData.debugMode) {
4339     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4340     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4341     setbuf(debugFP, NULL);
4342   }
4343
4344         gameInfo.outOfBook = NULL;
4345
4346         /* Do we have the ratings? */
4347         if (strcmp(player1Name, white) == 0 &&
4348             strcmp(player2Name, black) == 0) {
4349             if (appData.debugMode)
4350               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4351                       player1Rating, player2Rating);
4352             gameInfo.whiteRating = player1Rating;
4353             gameInfo.blackRating = player2Rating;
4354         } else if (strcmp(player2Name, white) == 0 &&
4355                    strcmp(player1Name, black) == 0) {
4356             if (appData.debugMode)
4357               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4358                       player2Rating, player1Rating);
4359             gameInfo.whiteRating = player2Rating;
4360             gameInfo.blackRating = player1Rating;
4361         }
4362         player1Name[0] = player2Name[0] = NULLCHAR;
4363
4364         /* Silence shouts if requested */
4365         if (appData.quietPlay &&
4366             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4367             SendToICS(ics_prefix);
4368             SendToICS("set shout 0\n");
4369         }
4370     }
4371
4372     /* Deal with midgame name changes */
4373     if (!newGame) {
4374         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4375             if (gameInfo.white) free(gameInfo.white);
4376             gameInfo.white = StrSave(white);
4377         }
4378         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4379             if (gameInfo.black) free(gameInfo.black);
4380             gameInfo.black = StrSave(black);
4381         }
4382     }
4383
4384     /* Throw away game result if anything actually changes in examine mode */
4385     if (gameMode == IcsExamining && !newGame) {
4386         gameInfo.result = GameUnfinished;
4387         if (gameInfo.resultDetails != NULL) {
4388             free(gameInfo.resultDetails);
4389             gameInfo.resultDetails = NULL;
4390         }
4391     }
4392
4393     /* In pausing && IcsExamining mode, we ignore boards coming
4394        in if they are in a different variation than we are. */
4395     if (pauseExamInvalid) return;
4396     if (pausing && gameMode == IcsExamining) {
4397         if (moveNum <= pauseExamForwardMostMove) {
4398             pauseExamInvalid = TRUE;
4399             forwardMostMove = pauseExamForwardMostMove;
4400             return;
4401         }
4402     }
4403
4404   if (appData.debugMode) {
4405     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4406   }
4407     /* Parse the board */
4408     for (k = 0; k < ranks; k++) {
4409       for (j = 0; j < files; j++)
4410         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4411       if(gameInfo.holdingsWidth > 1) {
4412            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4413            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4414       }
4415     }
4416     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4417       board[5][BOARD_RGHT+1] = WhiteAngel;
4418       board[6][BOARD_RGHT+1] = WhiteMarshall;
4419       board[1][0] = BlackMarshall;
4420       board[2][0] = BlackAngel;
4421       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4422     }
4423     CopyBoard(boards[moveNum], board);
4424     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4425     if (moveNum == 0) {
4426         startedFromSetupPosition =
4427           !CompareBoards(board, initialPosition);
4428         if(startedFromSetupPosition)
4429             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4430     }
4431
4432     /* [HGM] Set castling rights. Take the outermost Rooks,
4433        to make it also work for FRC opening positions. Note that board12
4434        is really defective for later FRC positions, as it has no way to
4435        indicate which Rook can castle if they are on the same side of King.
4436        For the initial position we grant rights to the outermost Rooks,
4437        and remember thos rights, and we then copy them on positions
4438        later in an FRC game. This means WB might not recognize castlings with
4439        Rooks that have moved back to their original position as illegal,
4440        but in ICS mode that is not its job anyway.
4441     */
4442     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4443     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4444
4445         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4446             if(board[0][i] == WhiteRook) j = i;
4447         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4448         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4449             if(board[0][i] == WhiteRook) j = i;
4450         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4451         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4452             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4453         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4454         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4455             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4456         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4457
4458         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4459         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4460         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4461             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4462         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4463             if(board[BOARD_HEIGHT-1][k] == bKing)
4464                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4465         if(gameInfo.variant == VariantTwoKings) {
4466             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4467             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4468             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4469         }
4470     } else { int r;
4471         r = boards[moveNum][CASTLING][0] = initialRights[0];
4472         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4473         r = boards[moveNum][CASTLING][1] = initialRights[1];
4474         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4475         r = boards[moveNum][CASTLING][3] = initialRights[3];
4476         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4477         r = boards[moveNum][CASTLING][4] = initialRights[4];
4478         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4479         /* wildcastle kludge: always assume King has rights */
4480         r = boards[moveNum][CASTLING][2] = initialRights[2];
4481         r = boards[moveNum][CASTLING][5] = initialRights[5];
4482     }
4483     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4484     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4485
4486
4487     if (ics_getting_history == H_GOT_REQ_HEADER ||
4488         ics_getting_history == H_GOT_UNREQ_HEADER) {
4489         /* This was an initial position from a move list, not
4490            the current position */
4491         return;
4492     }
4493
4494     /* Update currentMove and known move number limits */
4495     newMove = newGame || moveNum > forwardMostMove;
4496
4497     if (newGame) {
4498         forwardMostMove = backwardMostMove = currentMove = moveNum;
4499         if (gameMode == IcsExamining && moveNum == 0) {
4500           /* Workaround for ICS limitation: we are not told the wild
4501              type when starting to examine a game.  But if we ask for
4502              the move list, the move list header will tell us */
4503             ics_getting_history = H_REQUESTED;
4504             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4505             SendToICS(str);
4506         }
4507     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4508                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4509 #if ZIPPY
4510         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4511         /* [HGM] applied this also to an engine that is silently watching        */
4512         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4513             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4514             gameInfo.variant == currentlyInitializedVariant) {
4515           takeback = forwardMostMove - moveNum;
4516           for (i = 0; i < takeback; i++) {
4517             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4518             SendToProgram("undo\n", &first);
4519           }
4520         }
4521 #endif
4522
4523         forwardMostMove = moveNum;
4524         if (!pausing || currentMove > forwardMostMove)
4525           currentMove = forwardMostMove;
4526     } else {
4527         /* New part of history that is not contiguous with old part */
4528         if (pausing && gameMode == IcsExamining) {
4529             pauseExamInvalid = TRUE;
4530             forwardMostMove = pauseExamForwardMostMove;
4531             return;
4532         }
4533         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4534 #if ZIPPY
4535             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4536                 // [HGM] when we will receive the move list we now request, it will be
4537                 // fed to the engine from the first move on. So if the engine is not
4538                 // in the initial position now, bring it there.
4539                 InitChessProgram(&first, 0);
4540             }
4541 #endif
4542             ics_getting_history = H_REQUESTED;
4543             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4544             SendToICS(str);
4545         }
4546         forwardMostMove = backwardMostMove = currentMove = moveNum;
4547     }
4548
4549     /* Update the clocks */
4550     if (strchr(elapsed_time, '.')) {
4551       /* Time is in ms */
4552       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4553       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4554     } else {
4555       /* Time is in seconds */
4556       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4557       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4558     }
4559
4560
4561 #if ZIPPY
4562     if (appData.zippyPlay && newGame &&
4563         gameMode != IcsObserving && gameMode != IcsIdle &&
4564         gameMode != IcsExamining)
4565       ZippyFirstBoard(moveNum, basetime, increment);
4566 #endif
4567
4568     /* Put the move on the move list, first converting
4569        to canonical algebraic form. */
4570     if (moveNum > 0) {
4571   if (appData.debugMode) {
4572     if (appData.debugMode) { int f = forwardMostMove;
4573         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4574                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4575                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4576     }
4577     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4578     fprintf(debugFP, "moveNum = %d\n", moveNum);
4579     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4580     setbuf(debugFP, NULL);
4581   }
4582         if (moveNum <= backwardMostMove) {
4583             /* We don't know what the board looked like before
4584                this move.  Punt. */
4585           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4586             strcat(parseList[moveNum - 1], " ");
4587             strcat(parseList[moveNum - 1], elapsed_time);
4588             moveList[moveNum - 1][0] = NULLCHAR;
4589         } else if (strcmp(move_str, "none") == 0) {
4590             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4591             /* Again, we don't know what the board looked like;
4592                this is really the start of the game. */
4593             parseList[moveNum - 1][0] = NULLCHAR;
4594             moveList[moveNum - 1][0] = NULLCHAR;
4595             backwardMostMove = moveNum;
4596             startedFromSetupPosition = TRUE;
4597             fromX = fromY = toX = toY = -1;
4598         } else {
4599           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4600           //                 So we parse the long-algebraic move string in stead of the SAN move
4601           int valid; char buf[MSG_SIZ], *prom;
4602
4603           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4604                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4605           // str looks something like "Q/a1-a2"; kill the slash
4606           if(str[1] == '/')
4607             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4608           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4609           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4610                 strcat(buf, prom); // long move lacks promo specification!
4611           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4612                 if(appData.debugMode)
4613                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4614                 safeStrCpy(move_str, buf, MSG_SIZ);
4615           }
4616           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4617                                 &fromX, &fromY, &toX, &toY, &promoChar)
4618                || ParseOneMove(buf, moveNum - 1, &moveType,
4619                                 &fromX, &fromY, &toX, &toY, &promoChar);
4620           // end of long SAN patch
4621           if (valid) {
4622             (void) CoordsToAlgebraic(boards[moveNum - 1],
4623                                      PosFlags(moveNum - 1),
4624                                      fromY, fromX, toY, toX, promoChar,
4625                                      parseList[moveNum-1]);
4626             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4627               case MT_NONE:
4628               case MT_STALEMATE:
4629               default:
4630                 break;
4631               case MT_CHECK:
4632                 if(gameInfo.variant != VariantShogi)
4633                     strcat(parseList[moveNum - 1], "+");
4634                 break;
4635               case MT_CHECKMATE:
4636               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4637                 strcat(parseList[moveNum - 1], "#");
4638                 break;
4639             }
4640             strcat(parseList[moveNum - 1], " ");
4641             strcat(parseList[moveNum - 1], elapsed_time);
4642             /* currentMoveString is set as a side-effect of ParseOneMove */
4643             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4644             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4645             strcat(moveList[moveNum - 1], "\n");
4646
4647             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4648                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4649               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4650                 ChessSquare old, new = boards[moveNum][k][j];
4651                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4652                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4653                   if(old == new) continue;
4654                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4655                   else if(new == WhiteWazir || new == BlackWazir) {
4656                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4657                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4658                       else boards[moveNum][k][j] = old; // preserve type of Gold
4659                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4660                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4661               }
4662           } else {
4663             /* Move from ICS was illegal!?  Punt. */
4664             if (appData.debugMode) {
4665               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4666               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4667             }
4668             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4669             strcat(parseList[moveNum - 1], " ");
4670             strcat(parseList[moveNum - 1], elapsed_time);
4671             moveList[moveNum - 1][0] = NULLCHAR;
4672             fromX = fromY = toX = toY = -1;
4673           }
4674         }
4675   if (appData.debugMode) {
4676     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4677     setbuf(debugFP, NULL);
4678   }
4679
4680 #if ZIPPY
4681         /* Send move to chess program (BEFORE animating it). */
4682         if (appData.zippyPlay && !newGame && newMove &&
4683            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4684
4685             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4686                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4687                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4688                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4689                             move_str);
4690                     DisplayError(str, 0);
4691                 } else {
4692                     if (first.sendTime) {
4693                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4694                     }
4695                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4696                     if (firstMove && !bookHit) {
4697                         firstMove = FALSE;
4698                         if (first.useColors) {
4699                           SendToProgram(gameMode == IcsPlayingWhite ?
4700                                         "white\ngo\n" :
4701                                         "black\ngo\n", &first);
4702                         } else {
4703                           SendToProgram("go\n", &first);
4704                         }
4705                         first.maybeThinking = TRUE;
4706                     }
4707                 }
4708             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4709               if (moveList[moveNum - 1][0] == NULLCHAR) {
4710                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4711                 DisplayError(str, 0);
4712               } else {
4713                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4714                 SendMoveToProgram(moveNum - 1, &first);
4715               }
4716             }
4717         }
4718 #endif
4719     }
4720
4721     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4722         /* If move comes from a remote source, animate it.  If it
4723            isn't remote, it will have already been animated. */
4724         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4725             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4726         }
4727         if (!pausing && appData.highlightLastMove) {
4728             SetHighlights(fromX, fromY, toX, toY);
4729         }
4730     }
4731
4732     /* Start the clocks */
4733     whiteFlag = blackFlag = FALSE;
4734     appData.clockMode = !(basetime == 0 && increment == 0);
4735     if (ticking == 0) {
4736       ics_clock_paused = TRUE;
4737       StopClocks();
4738     } else if (ticking == 1) {
4739       ics_clock_paused = FALSE;
4740     }
4741     if (gameMode == IcsIdle ||
4742         relation == RELATION_OBSERVING_STATIC ||
4743         relation == RELATION_EXAMINING ||
4744         ics_clock_paused)
4745       DisplayBothClocks();
4746     else
4747       StartClocks();
4748
4749     /* Display opponents and material strengths */
4750     if (gameInfo.variant != VariantBughouse &&
4751         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4752         if (tinyLayout || smallLayout) {
4753             if(gameInfo.variant == VariantNormal)
4754               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4755                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4756                     basetime, increment);
4757             else
4758               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4759                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4760                     basetime, increment, (int) gameInfo.variant);
4761         } else {
4762             if(gameInfo.variant == VariantNormal)
4763               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4764                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4765                     basetime, increment);
4766             else
4767               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4768                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4769                     basetime, increment, VariantName(gameInfo.variant));
4770         }
4771         DisplayTitle(str);
4772   if (appData.debugMode) {
4773     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4774   }
4775     }
4776
4777
4778     /* Display the board */
4779     if (!pausing && !appData.noGUI) {
4780
4781       if (appData.premove)
4782           if (!gotPremove ||
4783              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4784              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4785               ClearPremoveHighlights();
4786
4787       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4788         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4789       DrawPosition(j, boards[currentMove]);
4790
4791       DisplayMove(moveNum - 1);
4792       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4793             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4794               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4795         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4796       }
4797     }
4798
4799     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4800 #if ZIPPY
4801     if(bookHit) { // [HGM] book: simulate book reply
4802         static char bookMove[MSG_SIZ]; // a bit generous?
4803
4804         programStats.nodes = programStats.depth = programStats.time =
4805         programStats.score = programStats.got_only_move = 0;
4806         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4807
4808         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4809         strcat(bookMove, bookHit);
4810         HandleMachineMove(bookMove, &first);
4811     }
4812 #endif
4813 }
4814
4815 void
4816 GetMoveListEvent ()
4817 {
4818     char buf[MSG_SIZ];
4819     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4820         ics_getting_history = H_REQUESTED;
4821         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4822         SendToICS(buf);
4823     }
4824 }
4825
4826 void
4827 AnalysisPeriodicEvent (int force)
4828 {
4829     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4830          && !force) || !appData.periodicUpdates)
4831       return;
4832
4833     /* Send . command to Crafty to collect stats */
4834     SendToProgram(".\n", &first);
4835
4836     /* Don't send another until we get a response (this makes
4837        us stop sending to old Crafty's which don't understand
4838        the "." command (sending illegal cmds resets node count & time,
4839        which looks bad)) */
4840     programStats.ok_to_send = 0;
4841 }
4842
4843 void
4844 ics_update_width (int new_width)
4845 {
4846         ics_printf("set width %d\n", new_width);
4847 }
4848
4849 void
4850 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4851 {
4852     char buf[MSG_SIZ];
4853
4854     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4855         // null move in variant where engine does not understand it (for analysis purposes)
4856         SendBoard(cps, moveNum + 1); // send position after move in stead.
4857         return;
4858     }
4859     if (cps->useUsermove) {
4860       SendToProgram("usermove ", cps);
4861     }
4862     if (cps->useSAN) {
4863       char *space;
4864       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4865         int len = space - parseList[moveNum];
4866         memcpy(buf, parseList[moveNum], len);
4867         buf[len++] = '\n';
4868         buf[len] = NULLCHAR;
4869       } else {
4870         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4871       }
4872       SendToProgram(buf, cps);
4873     } else {
4874       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4875         AlphaRank(moveList[moveNum], 4);
4876         SendToProgram(moveList[moveNum], cps);
4877         AlphaRank(moveList[moveNum], 4); // and back
4878       } else
4879       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4880        * the engine. It would be nice to have a better way to identify castle
4881        * moves here. */
4882       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4883                                                                          && cps->useOOCastle) {
4884         int fromX = moveList[moveNum][0] - AAA;
4885         int fromY = moveList[moveNum][1] - ONE;
4886         int toX = moveList[moveNum][2] - AAA;
4887         int toY = moveList[moveNum][3] - ONE;
4888         if((boards[moveNum][fromY][fromX] == WhiteKing
4889             && boards[moveNum][toY][toX] == WhiteRook)
4890            || (boards[moveNum][fromY][fromX] == BlackKing
4891                && boards[moveNum][toY][toX] == BlackRook)) {
4892           if(toX > fromX) SendToProgram("O-O\n", cps);
4893           else SendToProgram("O-O-O\n", cps);
4894         }
4895         else SendToProgram(moveList[moveNum], cps);
4896       } else
4897       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4898         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4899           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4900           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4901                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4902         } else
4903           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4904                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4905         SendToProgram(buf, cps);
4906       }
4907       else SendToProgram(moveList[moveNum], cps);
4908       /* End of additions by Tord */
4909     }
4910
4911     /* [HGM] setting up the opening has brought engine in force mode! */
4912     /*       Send 'go' if we are in a mode where machine should play. */
4913     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4914         (gameMode == TwoMachinesPlay   ||
4915 #if ZIPPY
4916          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4917 #endif
4918          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4919         SendToProgram("go\n", cps);
4920   if (appData.debugMode) {
4921     fprintf(debugFP, "(extra)\n");
4922   }
4923     }
4924     setboardSpoiledMachineBlack = 0;
4925 }
4926
4927 void
4928 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4929 {
4930     char user_move[MSG_SIZ];
4931     char suffix[4];
4932
4933     if(gameInfo.variant == VariantSChess && promoChar) {
4934         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4935         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4936     } else suffix[0] = NULLCHAR;
4937
4938     switch (moveType) {
4939       default:
4940         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4941                 (int)moveType, fromX, fromY, toX, toY);
4942         DisplayError(user_move + strlen("say "), 0);
4943         break;
4944       case WhiteKingSideCastle:
4945       case BlackKingSideCastle:
4946       case WhiteQueenSideCastleWild:
4947       case BlackQueenSideCastleWild:
4948       /* PUSH Fabien */
4949       case WhiteHSideCastleFR:
4950       case BlackHSideCastleFR:
4951       /* POP Fabien */
4952         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4953         break;
4954       case WhiteQueenSideCastle:
4955       case BlackQueenSideCastle:
4956       case WhiteKingSideCastleWild:
4957       case BlackKingSideCastleWild:
4958       /* PUSH Fabien */
4959       case WhiteASideCastleFR:
4960       case BlackASideCastleFR:
4961       /* POP Fabien */
4962         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
4963         break;
4964       case WhiteNonPromotion:
4965       case BlackNonPromotion:
4966         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4967         break;
4968       case WhitePromotion:
4969       case BlackPromotion:
4970         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4971           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4972                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4973                 PieceToChar(WhiteFerz));
4974         else if(gameInfo.variant == VariantGreat)
4975           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4976                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4977                 PieceToChar(WhiteMan));
4978         else
4979           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4980                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4981                 promoChar);
4982         break;
4983       case WhiteDrop:
4984       case BlackDrop:
4985       drop:
4986         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4987                  ToUpper(PieceToChar((ChessSquare) fromX)),
4988                  AAA + toX, ONE + toY);
4989         break;
4990       case IllegalMove:  /* could be a variant we don't quite understand */
4991         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4992       case NormalMove:
4993       case WhiteCapturesEnPassant:
4994       case BlackCapturesEnPassant:
4995         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4996                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4997         break;
4998     }
4999     SendToICS(user_move);
5000     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5001         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5002 }
5003
5004 void
5005 UploadGameEvent ()
5006 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5007     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5008     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5009     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5010       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5011       return;
5012     }
5013     if(gameMode != IcsExamining) { // is this ever not the case?
5014         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5015
5016         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5017           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5018         } else { // on FICS we must first go to general examine mode
5019           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5020         }
5021         if(gameInfo.variant != VariantNormal) {
5022             // try figure out wild number, as xboard names are not always valid on ICS
5023             for(i=1; i<=36; i++) {
5024               snprintf(buf, MSG_SIZ, "wild/%d", i);
5025                 if(StringToVariant(buf) == gameInfo.variant) break;
5026             }
5027             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5028             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5029             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5030         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5031         SendToICS(ics_prefix);
5032         SendToICS(buf);
5033         if(startedFromSetupPosition || backwardMostMove != 0) {
5034           fen = PositionToFEN(backwardMostMove, NULL);
5035           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5036             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5037             SendToICS(buf);
5038           } else { // FICS: everything has to set by separate bsetup commands
5039             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5040             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5041             SendToICS(buf);
5042             if(!WhiteOnMove(backwardMostMove)) {
5043                 SendToICS("bsetup tomove black\n");
5044             }
5045             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5046             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5047             SendToICS(buf);
5048             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5049             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5050             SendToICS(buf);
5051             i = boards[backwardMostMove][EP_STATUS];
5052             if(i >= 0) { // set e.p.
5053               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5054                 SendToICS(buf);
5055             }
5056             bsetup++;
5057           }
5058         }
5059       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5060             SendToICS("bsetup done\n"); // switch to normal examining.
5061     }
5062     for(i = backwardMostMove; i<last; i++) {
5063         char buf[20];
5064         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5065         SendToICS(buf);
5066     }
5067     SendToICS(ics_prefix);
5068     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5069 }
5070
5071 void
5072 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5073 {
5074     if (rf == DROP_RANK) {
5075       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5076       sprintf(move, "%c@%c%c\n",
5077                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5078     } else {
5079         if (promoChar == 'x' || promoChar == NULLCHAR) {
5080           sprintf(move, "%c%c%c%c\n",
5081                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5082         } else {
5083             sprintf(move, "%c%c%c%c%c\n",
5084                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5085         }
5086     }
5087 }
5088
5089 void
5090 ProcessICSInitScript (FILE *f)
5091 {
5092     char buf[MSG_SIZ];
5093
5094     while (fgets(buf, MSG_SIZ, f)) {
5095         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5096     }
5097
5098     fclose(f);
5099 }
5100
5101
5102 static int lastX, lastY, selectFlag, dragging;
5103
5104 void
5105 Sweep (int step)
5106 {
5107     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5108     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5109     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5110     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5111     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5112     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5113     do {
5114         promoSweep -= step;
5115         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5116         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5117         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5118         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5119         if(!step) step = -1;
5120     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5121             appData.testLegality && (promoSweep == king ||
5122             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5123     ChangeDragPiece(promoSweep);
5124 }
5125
5126 int
5127 PromoScroll (int x, int y)
5128 {
5129   int step = 0;
5130
5131   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5132   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5133   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5134   if(!step) return FALSE;
5135   lastX = x; lastY = y;
5136   if((promoSweep < BlackPawn) == flipView) step = -step;
5137   if(step > 0) selectFlag = 1;
5138   if(!selectFlag) Sweep(step);
5139   return FALSE;
5140 }
5141
5142 void
5143 NextPiece (int step)
5144 {
5145     ChessSquare piece = boards[currentMove][toY][toX];
5146     do {
5147         pieceSweep -= step;
5148         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5149         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5150         if(!step) step = -1;
5151     } while(PieceToChar(pieceSweep) == '.');
5152     boards[currentMove][toY][toX] = pieceSweep;
5153     DrawPosition(FALSE, boards[currentMove]);
5154     boards[currentMove][toY][toX] = piece;
5155 }
5156 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5157 void
5158 AlphaRank (char *move, int n)
5159 {
5160 //    char *p = move, c; int x, y;
5161
5162     if (appData.debugMode) {
5163         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5164     }
5165
5166     if(move[1]=='*' &&
5167        move[2]>='0' && move[2]<='9' &&
5168        move[3]>='a' && move[3]<='x'    ) {
5169         move[1] = '@';
5170         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5171         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5172     } else
5173     if(move[0]>='0' && move[0]<='9' &&
5174        move[1]>='a' && move[1]<='x' &&
5175        move[2]>='0' && move[2]<='9' &&
5176        move[3]>='a' && move[3]<='x'    ) {
5177         /* input move, Shogi -> normal */
5178         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5179         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5180         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5181         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5182     } else
5183     if(move[1]=='@' &&
5184        move[3]>='0' && move[3]<='9' &&
5185        move[2]>='a' && move[2]<='x'    ) {
5186         move[1] = '*';
5187         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5188         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5189     } else
5190     if(
5191        move[0]>='a' && move[0]<='x' &&
5192        move[3]>='0' && move[3]<='9' &&
5193        move[2]>='a' && move[2]<='x'    ) {
5194          /* output move, normal -> Shogi */
5195         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5196         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5197         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5198         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5199         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5200     }
5201     if (appData.debugMode) {
5202         fprintf(debugFP, "   out = '%s'\n", move);
5203     }
5204 }
5205
5206 char yy_textstr[8000];
5207
5208 /* Parser for moves from gnuchess, ICS, or user typein box */
5209 Boolean
5210 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5211 {
5212     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5213
5214     switch (*moveType) {
5215       case WhitePromotion:
5216       case BlackPromotion:
5217       case WhiteNonPromotion:
5218       case BlackNonPromotion:
5219       case NormalMove:
5220       case WhiteCapturesEnPassant:
5221       case BlackCapturesEnPassant:
5222       case WhiteKingSideCastle:
5223       case WhiteQueenSideCastle:
5224       case BlackKingSideCastle:
5225       case BlackQueenSideCastle:
5226       case WhiteKingSideCastleWild:
5227       case WhiteQueenSideCastleWild:
5228       case BlackKingSideCastleWild:
5229       case BlackQueenSideCastleWild:
5230       /* Code added by Tord: */
5231       case WhiteHSideCastleFR:
5232       case WhiteASideCastleFR:
5233       case BlackHSideCastleFR:
5234       case BlackASideCastleFR:
5235       /* End of code added by Tord */
5236       case IllegalMove:         /* bug or odd chess variant */
5237         *fromX = currentMoveString[0] - AAA;
5238         *fromY = currentMoveString[1] - ONE;
5239         *toX = currentMoveString[2] - AAA;
5240         *toY = currentMoveString[3] - ONE;
5241         *promoChar = currentMoveString[4];
5242         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5243             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5244     if (appData.debugMode) {
5245         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5246     }
5247             *fromX = *fromY = *toX = *toY = 0;
5248             return FALSE;
5249         }
5250         if (appData.testLegality) {
5251           return (*moveType != IllegalMove);
5252         } else {
5253           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5254                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5255         }
5256
5257       case WhiteDrop:
5258       case BlackDrop:
5259         *fromX = *moveType == WhiteDrop ?
5260           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5261           (int) CharToPiece(ToLower(currentMoveString[0]));
5262         *fromY = DROP_RANK;
5263         *toX = currentMoveString[2] - AAA;
5264         *toY = currentMoveString[3] - ONE;
5265         *promoChar = NULLCHAR;
5266         return TRUE;
5267
5268       case AmbiguousMove:
5269       case ImpossibleMove:
5270       case EndOfFile:
5271       case ElapsedTime:
5272       case Comment:
5273       case PGNTag:
5274       case NAG:
5275       case WhiteWins:
5276       case BlackWins:
5277       case GameIsDrawn:
5278       default:
5279     if (appData.debugMode) {
5280         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5281     }
5282         /* bug? */
5283         *fromX = *fromY = *toX = *toY = 0;
5284         *promoChar = NULLCHAR;
5285         return FALSE;
5286     }
5287 }
5288
5289 Boolean pushed = FALSE;
5290 char *lastParseAttempt;
5291
5292 void
5293 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5294 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5295   int fromX, fromY, toX, toY; char promoChar;
5296   ChessMove moveType;
5297   Boolean valid;
5298   int nr = 0;
5299
5300   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5301     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5302     pushed = TRUE;
5303   }
5304   endPV = forwardMostMove;
5305   do {
5306     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5307     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5308     lastParseAttempt = pv;
5309     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5310 if(appData.debugMode){
5311 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);
5312 }
5313     if(!valid && nr == 0 &&
5314        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5315         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5316         // Hande case where played move is different from leading PV move
5317         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5318         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5319         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5320         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5321           endPV += 2; // if position different, keep this
5322           moveList[endPV-1][0] = fromX + AAA;
5323           moveList[endPV-1][1] = fromY + ONE;
5324           moveList[endPV-1][2] = toX + AAA;
5325           moveList[endPV-1][3] = toY + ONE;
5326           parseList[endPV-1][0] = NULLCHAR;
5327           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5328         }
5329       }
5330     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5331     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5332     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5333     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5334         valid++; // allow comments in PV
5335         continue;
5336     }
5337     nr++;
5338     if(endPV+1 > framePtr) break; // no space, truncate
5339     if(!valid) break;
5340     endPV++;
5341     CopyBoard(boards[endPV], boards[endPV-1]);
5342     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5343     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5344     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5345     CoordsToAlgebraic(boards[endPV - 1],
5346                              PosFlags(endPV - 1),
5347                              fromY, fromX, toY, toX, promoChar,
5348                              parseList[endPV - 1]);
5349   } while(valid);
5350   if(atEnd == 2) return; // used hidden, for PV conversion
5351   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5352   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5353   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5354                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5355   DrawPosition(TRUE, boards[currentMove]);
5356 }
5357
5358 int
5359 MultiPV (ChessProgramState *cps)
5360 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5361         int i;
5362         for(i=0; i<cps->nrOptions; i++)
5363             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5364                 return i;
5365         return -1;
5366 }
5367
5368 Boolean
5369 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5370 {
5371         int startPV, multi, lineStart, origIndex = index;
5372         char *p, buf2[MSG_SIZ];
5373
5374         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5375         lastX = x; lastY = y;
5376         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5377         lineStart = startPV = index;
5378         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5379         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5380         index = startPV;
5381         do{ while(buf[index] && buf[index] != '\n') index++;
5382         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5383         buf[index] = 0;
5384         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5385                 int n = first.option[multi].value;
5386                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5387                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5388                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5389                 first.option[multi].value = n;
5390                 *start = *end = 0;
5391                 return FALSE;
5392         }
5393         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5394         *start = startPV; *end = index-1;
5395         return TRUE;
5396 }
5397
5398 char *
5399 PvToSAN (char *pv)
5400 {
5401         static char buf[10*MSG_SIZ];
5402         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5403         *buf = NULLCHAR;
5404         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5405         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5406         for(i = forwardMostMove; i<endPV; i++){
5407             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5408             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5409             k += strlen(buf+k);
5410         }
5411         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5412         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5413         endPV = savedEnd;
5414         return buf;
5415 }
5416
5417 Boolean
5418 LoadPV (int x, int y)
5419 { // called on right mouse click to load PV
5420   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5421   lastX = x; lastY = y;
5422   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5423   return TRUE;
5424 }
5425
5426 void
5427 UnLoadPV ()
5428 {
5429   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5430   if(endPV < 0) return;
5431   endPV = -1;
5432   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5433         Boolean saveAnimate = appData.animate;
5434         if(pushed) {
5435             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5436                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5437             } else storedGames--; // abandon shelved tail of original game
5438         }
5439         pushed = FALSE;
5440         forwardMostMove = currentMove;
5441         currentMove = oldFMM;
5442         appData.animate = FALSE;
5443         ToNrEvent(forwardMostMove);
5444         appData.animate = saveAnimate;
5445   }
5446   currentMove = forwardMostMove;
5447   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5448   ClearPremoveHighlights();
5449   DrawPosition(TRUE, boards[currentMove]);
5450 }
5451
5452 void
5453 MovePV (int x, int y, int h)
5454 { // step through PV based on mouse coordinates (called on mouse move)
5455   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5456
5457   // we must somehow check if right button is still down (might be released off board!)
5458   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5459   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5460   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5461   if(!step) return;
5462   lastX = x; lastY = y;
5463
5464   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5465   if(endPV < 0) return;
5466   if(y < margin) step = 1; else
5467   if(y > h - margin) step = -1;
5468   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5469   currentMove += step;
5470   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5471   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5472                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5473   DrawPosition(FALSE, boards[currentMove]);
5474 }
5475
5476
5477 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5478 // All positions will have equal probability, but the current method will not provide a unique
5479 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5480 #define DARK 1
5481 #define LITE 2
5482 #define ANY 3
5483
5484 int squaresLeft[4];
5485 int piecesLeft[(int)BlackPawn];
5486 int seed, nrOfShuffles;
5487
5488 void
5489 GetPositionNumber ()
5490 {       // sets global variable seed
5491         int i;
5492
5493         seed = appData.defaultFrcPosition;
5494         if(seed < 0) { // randomize based on time for negative FRC position numbers
5495                 for(i=0; i<50; i++) seed += random();
5496                 seed = random() ^ random() >> 8 ^ random() << 8;
5497                 if(seed<0) seed = -seed;
5498         }
5499 }
5500
5501 int
5502 put (Board board, int pieceType, int rank, int n, int shade)
5503 // put the piece on the (n-1)-th empty squares of the given shade
5504 {
5505         int i;
5506
5507         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5508                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5509                         board[rank][i] = (ChessSquare) pieceType;
5510                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5511                         squaresLeft[ANY]--;
5512                         piecesLeft[pieceType]--;
5513                         return i;
5514                 }
5515         }
5516         return -1;
5517 }
5518
5519
5520 void
5521 AddOnePiece (Board board, int pieceType, int rank, int shade)
5522 // calculate where the next piece goes, (any empty square), and put it there
5523 {
5524         int i;
5525
5526         i = seed % squaresLeft[shade];
5527         nrOfShuffles *= squaresLeft[shade];
5528         seed /= squaresLeft[shade];
5529         put(board, pieceType, rank, i, shade);
5530 }
5531
5532 void
5533 AddTwoPieces (Board board, int pieceType, int rank)
5534 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5535 {
5536         int i, n=squaresLeft[ANY], j=n-1, k;
5537
5538         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5539         i = seed % k;  // pick one
5540         nrOfShuffles *= k;
5541         seed /= k;
5542         while(i >= j) i -= j--;
5543         j = n - 1 - j; i += j;
5544         put(board, pieceType, rank, j, ANY);
5545         put(board, pieceType, rank, i, ANY);
5546 }
5547
5548 void
5549 SetUpShuffle (Board board, int number)
5550 {
5551         int i, p, first=1;
5552
5553         GetPositionNumber(); nrOfShuffles = 1;
5554
5555         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5556         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5557         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5558
5559         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5560
5561         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5562             p = (int) board[0][i];
5563             if(p < (int) BlackPawn) piecesLeft[p] ++;
5564             board[0][i] = EmptySquare;
5565         }
5566
5567         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5568             // shuffles restricted to allow normal castling put KRR first
5569             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5570                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5571             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5572                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5573             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5574                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5575             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5576                 put(board, WhiteRook, 0, 0, ANY);
5577             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5578         }
5579
5580         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5581             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5582             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5583                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5584                 while(piecesLeft[p] >= 2) {
5585                     AddOnePiece(board, p, 0, LITE);
5586                     AddOnePiece(board, p, 0, DARK);
5587                 }
5588                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5589             }
5590
5591         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5592             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5593             // but we leave King and Rooks for last, to possibly obey FRC restriction
5594             if(p == (int)WhiteRook) continue;
5595             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5596             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5597         }
5598
5599         // now everything is placed, except perhaps King (Unicorn) and Rooks
5600
5601         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5602             // Last King gets castling rights
5603             while(piecesLeft[(int)WhiteUnicorn]) {
5604                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5605                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5606             }
5607
5608             while(piecesLeft[(int)WhiteKing]) {
5609                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5610                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5611             }
5612
5613
5614         } else {
5615             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5616             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5617         }
5618
5619         // Only Rooks can be left; simply place them all
5620         while(piecesLeft[(int)WhiteRook]) {
5621                 i = put(board, WhiteRook, 0, 0, ANY);
5622                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5623                         if(first) {
5624                                 first=0;
5625                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5626                         }
5627                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5628                 }
5629         }
5630         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5631             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5632         }
5633
5634         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5635 }
5636
5637 int
5638 SetCharTable (char *table, const char * map)
5639 /* [HGM] moved here from winboard.c because of its general usefulness */
5640 /*       Basically a safe strcpy that uses the last character as King */
5641 {
5642     int result = FALSE; int NrPieces;
5643
5644     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5645                     && NrPieces >= 12 && !(NrPieces&1)) {
5646         int i; /* [HGM] Accept even length from 12 to 34 */
5647
5648         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5649         for( i=0; i<NrPieces/2-1; i++ ) {
5650             table[i] = map[i];
5651             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5652         }
5653         table[(int) WhiteKing]  = map[NrPieces/2-1];
5654         table[(int) BlackKing]  = map[NrPieces-1];
5655
5656         result = TRUE;
5657     }
5658
5659     return result;
5660 }
5661
5662 void
5663 Prelude (Board board)
5664 {       // [HGM] superchess: random selection of exo-pieces
5665         int i, j, k; ChessSquare p;
5666         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5667
5668         GetPositionNumber(); // use FRC position number
5669
5670         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5671             SetCharTable(pieceToChar, appData.pieceToCharTable);
5672             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5673                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5674         }
5675
5676         j = seed%4;                 seed /= 4;
5677         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5678         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5679         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5680         j = seed%3 + (seed%3 >= j); seed /= 3;
5681         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5682         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5683         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5684         j = seed%3;                 seed /= 3;
5685         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5686         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5687         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5688         j = seed%2 + (seed%2 >= j); seed /= 2;
5689         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5690         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5691         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5692         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5693         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5694         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5695         put(board, exoPieces[0],    0, 0, ANY);
5696         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5697 }
5698
5699 void
5700 InitPosition (int redraw)
5701 {
5702     ChessSquare (* pieces)[BOARD_FILES];
5703     int i, j, pawnRow, overrule,
5704     oldx = gameInfo.boardWidth,
5705     oldy = gameInfo.boardHeight,
5706     oldh = gameInfo.holdingsWidth;
5707     static int oldv;
5708
5709     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5710
5711     /* [AS] Initialize pv info list [HGM] and game status */
5712     {
5713         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5714             pvInfoList[i].depth = 0;
5715             boards[i][EP_STATUS] = EP_NONE;
5716             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5717         }
5718
5719         initialRulePlies = 0; /* 50-move counter start */
5720
5721         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5722         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5723     }
5724
5725
5726     /* [HGM] logic here is completely changed. In stead of full positions */
5727     /* the initialized data only consist of the two backranks. The switch */
5728     /* selects which one we will use, which is than copied to the Board   */
5729     /* initialPosition, which for the rest is initialized by Pawns and    */
5730     /* empty squares. This initial position is then copied to boards[0],  */
5731     /* possibly after shuffling, so that it remains available.            */
5732
5733     gameInfo.holdingsWidth = 0; /* default board sizes */
5734     gameInfo.boardWidth    = 8;
5735     gameInfo.boardHeight   = 8;
5736     gameInfo.holdingsSize  = 0;
5737     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5738     for(i=0; i<BOARD_FILES-2; i++)
5739       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5740     initialPosition[EP_STATUS] = EP_NONE;
5741     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5742     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5743          SetCharTable(pieceNickName, appData.pieceNickNames);
5744     else SetCharTable(pieceNickName, "............");
5745     pieces = FIDEArray;
5746
5747     switch (gameInfo.variant) {
5748     case VariantFischeRandom:
5749       shuffleOpenings = TRUE;
5750     default:
5751       break;
5752     case VariantShatranj:
5753       pieces = ShatranjArray;
5754       nrCastlingRights = 0;
5755       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5756       break;
5757     case VariantMakruk:
5758       pieces = makrukArray;
5759       nrCastlingRights = 0;
5760       startedFromSetupPosition = TRUE;
5761       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5762       break;
5763     case VariantTwoKings:
5764       pieces = twoKingsArray;
5765       break;
5766     case VariantGrand:
5767       pieces = GrandArray;
5768       nrCastlingRights = 0;
5769       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5770       gameInfo.boardWidth = 10;
5771       gameInfo.boardHeight = 10;
5772       gameInfo.holdingsSize = 7;
5773       break;
5774     case VariantCapaRandom:
5775       shuffleOpenings = TRUE;
5776     case VariantCapablanca:
5777       pieces = CapablancaArray;
5778       gameInfo.boardWidth = 10;
5779       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5780       break;
5781     case VariantGothic:
5782       pieces = GothicArray;
5783       gameInfo.boardWidth = 10;
5784       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5785       break;
5786     case VariantSChess:
5787       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5788       gameInfo.holdingsSize = 7;
5789       break;
5790     case VariantJanus:
5791       pieces = JanusArray;
5792       gameInfo.boardWidth = 10;
5793       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5794       nrCastlingRights = 6;
5795         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5796         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5797         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5798         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5799         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5800         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5801       break;
5802     case VariantFalcon:
5803       pieces = FalconArray;
5804       gameInfo.boardWidth = 10;
5805       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5806       break;
5807     case VariantXiangqi:
5808       pieces = XiangqiArray;
5809       gameInfo.boardWidth  = 9;
5810       gameInfo.boardHeight = 10;
5811       nrCastlingRights = 0;
5812       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5813       break;
5814     case VariantShogi:
5815       pieces = ShogiArray;
5816       gameInfo.boardWidth  = 9;
5817       gameInfo.boardHeight = 9;
5818       gameInfo.holdingsSize = 7;
5819       nrCastlingRights = 0;
5820       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5821       break;
5822     case VariantCourier:
5823       pieces = CourierArray;
5824       gameInfo.boardWidth  = 12;
5825       nrCastlingRights = 0;
5826       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5827       break;
5828     case VariantKnightmate:
5829       pieces = KnightmateArray;
5830       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5831       break;
5832     case VariantSpartan:
5833       pieces = SpartanArray;
5834       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5835       break;
5836     case VariantFairy:
5837       pieces = fairyArray;
5838       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5839       break;
5840     case VariantGreat:
5841       pieces = GreatArray;
5842       gameInfo.boardWidth = 10;
5843       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5844       gameInfo.holdingsSize = 8;
5845       break;
5846     case VariantSuper:
5847       pieces = FIDEArray;
5848       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5849       gameInfo.holdingsSize = 8;
5850       startedFromSetupPosition = TRUE;
5851       break;
5852     case VariantCrazyhouse:
5853     case VariantBughouse:
5854       pieces = FIDEArray;
5855       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5856       gameInfo.holdingsSize = 5;
5857       break;
5858     case VariantWildCastle:
5859       pieces = FIDEArray;
5860       /* !!?shuffle with kings guaranteed to be on d or e file */
5861       shuffleOpenings = 1;
5862       break;
5863     case VariantNoCastle:
5864       pieces = FIDEArray;
5865       nrCastlingRights = 0;
5866       /* !!?unconstrained back-rank shuffle */
5867       shuffleOpenings = 1;
5868       break;
5869     }
5870
5871     overrule = 0;
5872     if(appData.NrFiles >= 0) {
5873         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5874         gameInfo.boardWidth = appData.NrFiles;
5875     }
5876     if(appData.NrRanks >= 0) {
5877         gameInfo.boardHeight = appData.NrRanks;
5878     }
5879     if(appData.holdingsSize >= 0) {
5880         i = appData.holdingsSize;
5881         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5882         gameInfo.holdingsSize = i;
5883     }
5884     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5885     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5886         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5887
5888     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5889     if(pawnRow < 1) pawnRow = 1;
5890     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5891
5892     /* User pieceToChar list overrules defaults */
5893     if(appData.pieceToCharTable != NULL)
5894         SetCharTable(pieceToChar, appData.pieceToCharTable);
5895
5896     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5897
5898         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5899             s = (ChessSquare) 0; /* account holding counts in guard band */
5900         for( i=0; i<BOARD_HEIGHT; i++ )
5901             initialPosition[i][j] = s;
5902
5903         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5904         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5905         initialPosition[pawnRow][j] = WhitePawn;
5906         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5907         if(gameInfo.variant == VariantXiangqi) {
5908             if(j&1) {
5909                 initialPosition[pawnRow][j] =
5910                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5911                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5912                    initialPosition[2][j] = WhiteCannon;
5913                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5914                 }
5915             }
5916         }
5917         if(gameInfo.variant == VariantGrand) {
5918             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5919                initialPosition[0][j] = WhiteRook;
5920                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5921             }
5922         }
5923         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5924     }
5925     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5926
5927             j=BOARD_LEFT+1;
5928             initialPosition[1][j] = WhiteBishop;
5929             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5930             j=BOARD_RGHT-2;
5931             initialPosition[1][j] = WhiteRook;
5932             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5933     }
5934
5935     if( nrCastlingRights == -1) {
5936         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5937         /*       This sets default castling rights from none to normal corners   */
5938         /* Variants with other castling rights must set them themselves above    */
5939         nrCastlingRights = 6;
5940
5941         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5942         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5943         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5944         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5945         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5946         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5947      }
5948
5949      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5950      if(gameInfo.variant == VariantGreat) { // promotion commoners
5951         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5952         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5953         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5954         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5955      }
5956      if( gameInfo.variant == VariantSChess ) {
5957       initialPosition[1][0] = BlackMarshall;
5958       initialPosition[2][0] = BlackAngel;
5959       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5960       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5961       initialPosition[1][1] = initialPosition[2][1] = 
5962       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5963      }
5964   if (appData.debugMode) {
5965     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5966   }
5967     if(shuffleOpenings) {
5968         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5969         startedFromSetupPosition = TRUE;
5970     }
5971     if(startedFromPositionFile) {
5972       /* [HGM] loadPos: use PositionFile for every new game */
5973       CopyBoard(initialPosition, filePosition);
5974       for(i=0; i<nrCastlingRights; i++)
5975           initialRights[i] = filePosition[CASTLING][i];
5976       startedFromSetupPosition = TRUE;
5977     }
5978
5979     CopyBoard(boards[0], initialPosition);
5980
5981     if(oldx != gameInfo.boardWidth ||
5982        oldy != gameInfo.boardHeight ||
5983        oldv != gameInfo.variant ||
5984        oldh != gameInfo.holdingsWidth
5985                                          )
5986             InitDrawingSizes(-2 ,0);
5987
5988     oldv = gameInfo.variant;
5989     if (redraw)
5990       DrawPosition(TRUE, boards[currentMove]);
5991 }
5992
5993 void
5994 SendBoard (ChessProgramState *cps, int moveNum)
5995 {
5996     char message[MSG_SIZ];
5997
5998     if (cps->useSetboard) {
5999       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6000       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6001       SendToProgram(message, cps);
6002       free(fen);
6003
6004     } else {
6005       ChessSquare *bp;
6006       int i, j, left=0, right=BOARD_WIDTH;
6007       /* Kludge to set black to move, avoiding the troublesome and now
6008        * deprecated "black" command.
6009        */
6010       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6011         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6012
6013       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6014
6015       SendToProgram("edit\n", cps);
6016       SendToProgram("#\n", cps);
6017       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6018         bp = &boards[moveNum][i][left];
6019         for (j = left; j < right; j++, bp++) {
6020           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6021           if ((int) *bp < (int) BlackPawn) {
6022             if(j == BOARD_RGHT+1)
6023                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6024             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6025             if(message[0] == '+' || message[0] == '~') {
6026               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6027                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6028                         AAA + j, ONE + i);
6029             }
6030             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6031                 message[1] = BOARD_RGHT   - 1 - j + '1';
6032                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6033             }
6034             SendToProgram(message, cps);
6035           }
6036         }
6037       }
6038
6039       SendToProgram("c\n", cps);
6040       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6041         bp = &boards[moveNum][i][left];
6042         for (j = left; j < right; j++, bp++) {
6043           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6044           if (((int) *bp != (int) EmptySquare)
6045               && ((int) *bp >= (int) BlackPawn)) {
6046             if(j == BOARD_LEFT-2)
6047                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6048             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6049                     AAA + j, ONE + i);
6050             if(message[0] == '+' || message[0] == '~') {
6051               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6052                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6053                         AAA + j, ONE + i);
6054             }
6055             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6056                 message[1] = BOARD_RGHT   - 1 - j + '1';
6057                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6058             }
6059             SendToProgram(message, cps);
6060           }
6061         }
6062       }
6063
6064       SendToProgram(".\n", cps);
6065     }
6066     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6067 }
6068
6069 ChessSquare
6070 DefaultPromoChoice (int white)
6071 {
6072     ChessSquare result;
6073     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6074         result = WhiteFerz; // no choice
6075     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6076         result= WhiteKing; // in Suicide Q is the last thing we want
6077     else if(gameInfo.variant == VariantSpartan)
6078         result = white ? WhiteQueen : WhiteAngel;
6079     else result = WhiteQueen;
6080     if(!white) result = WHITE_TO_BLACK result;
6081     return result;
6082 }
6083
6084 static int autoQueen; // [HGM] oneclick
6085
6086 int
6087 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6088 {
6089     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6090     /* [HGM] add Shogi promotions */
6091     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6092     ChessSquare piece;
6093     ChessMove moveType;
6094     Boolean premove;
6095
6096     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6097     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6098
6099     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6100       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6101         return FALSE;
6102
6103     piece = boards[currentMove][fromY][fromX];
6104     if(gameInfo.variant == VariantShogi) {
6105         promotionZoneSize = BOARD_HEIGHT/3;
6106         highestPromotingPiece = (int)WhiteFerz;
6107     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6108         promotionZoneSize = 3;
6109     }
6110
6111     // Treat Lance as Pawn when it is not representing Amazon
6112     if(gameInfo.variant != VariantSuper) {
6113         if(piece == WhiteLance) piece = WhitePawn; else
6114         if(piece == BlackLance) piece = BlackPawn;
6115     }
6116
6117     // next weed out all moves that do not touch the promotion zone at all
6118     if((int)piece >= BlackPawn) {
6119         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6120              return FALSE;
6121         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6122     } else {
6123         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6124            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6125     }
6126
6127     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6128
6129     // weed out mandatory Shogi promotions
6130     if(gameInfo.variant == VariantShogi) {
6131         if(piece >= BlackPawn) {
6132             if(toY == 0 && piece == BlackPawn ||
6133                toY == 0 && piece == BlackQueen ||
6134                toY <= 1 && piece == BlackKnight) {
6135                 *promoChoice = '+';
6136                 return FALSE;
6137             }
6138         } else {
6139             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6140                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6141                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6142                 *promoChoice = '+';
6143                 return FALSE;
6144             }
6145         }
6146     }
6147
6148     // weed out obviously illegal Pawn moves
6149     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6150         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6151         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6152         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6153         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6154         // note we are not allowed to test for valid (non-)capture, due to premove
6155     }
6156
6157     // we either have a choice what to promote to, or (in Shogi) whether to promote
6158     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6159         *promoChoice = PieceToChar(BlackFerz);  // no choice
6160         return FALSE;
6161     }
6162     // no sense asking what we must promote to if it is going to explode...
6163     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6164         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6165         return FALSE;
6166     }
6167     // give caller the default choice even if we will not make it
6168     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6169     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6170     if(        sweepSelect && gameInfo.variant != VariantGreat
6171                            && gameInfo.variant != VariantGrand
6172                            && gameInfo.variant != VariantSuper) return FALSE;
6173     if(autoQueen) return FALSE; // predetermined
6174
6175     // suppress promotion popup on illegal moves that are not premoves
6176     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6177               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6178     if(appData.testLegality && !premove) {
6179         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6180                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6181         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6182             return FALSE;
6183     }
6184
6185     return TRUE;
6186 }
6187
6188 int
6189 InPalace (int row, int column)
6190 {   /* [HGM] for Xiangqi */
6191     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6192          column < (BOARD_WIDTH + 4)/2 &&
6193          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6194     return FALSE;
6195 }
6196
6197 int
6198 PieceForSquare (int x, int y)
6199 {
6200   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6201      return -1;
6202   else
6203      return boards[currentMove][y][x];
6204 }
6205
6206 int
6207 OKToStartUserMove (int x, int y)
6208 {
6209     ChessSquare from_piece;
6210     int white_piece;
6211
6212     if (matchMode) return FALSE;
6213     if (gameMode == EditPosition) return TRUE;
6214
6215     if (x >= 0 && y >= 0)
6216       from_piece = boards[currentMove][y][x];
6217     else
6218       from_piece = EmptySquare;
6219
6220     if (from_piece == EmptySquare) return FALSE;
6221
6222     white_piece = (int)from_piece >= (int)WhitePawn &&
6223       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6224
6225     switch (gameMode) {
6226       case AnalyzeFile:
6227       case TwoMachinesPlay:
6228       case EndOfGame:
6229         return FALSE;
6230
6231       case IcsObserving:
6232       case IcsIdle:
6233         return FALSE;
6234
6235       case MachinePlaysWhite:
6236       case IcsPlayingBlack:
6237         if (appData.zippyPlay) return FALSE;
6238         if (white_piece) {
6239             DisplayMoveError(_("You are playing Black"));
6240             return FALSE;
6241         }
6242         break;
6243
6244       case MachinePlaysBlack:
6245       case IcsPlayingWhite:
6246         if (appData.zippyPlay) return FALSE;
6247         if (!white_piece) {
6248             DisplayMoveError(_("You are playing White"));
6249             return FALSE;
6250         }
6251         break;
6252
6253       case PlayFromGameFile:
6254             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6255       case EditGame:
6256         if (!white_piece && WhiteOnMove(currentMove)) {
6257             DisplayMoveError(_("It is White's turn"));
6258             return FALSE;
6259         }
6260         if (white_piece && !WhiteOnMove(currentMove)) {
6261             DisplayMoveError(_("It is Black's turn"));
6262             return FALSE;
6263         }
6264         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6265             /* Editing correspondence game history */
6266             /* Could disallow this or prompt for confirmation */
6267             cmailOldMove = -1;
6268         }
6269         break;
6270
6271       case BeginningOfGame:
6272         if (appData.icsActive) return FALSE;
6273         if (!appData.noChessProgram) {
6274             if (!white_piece) {
6275                 DisplayMoveError(_("You are playing White"));
6276                 return FALSE;
6277             }
6278         }
6279         break;
6280
6281       case Training:
6282         if (!white_piece && WhiteOnMove(currentMove)) {
6283             DisplayMoveError(_("It is White's turn"));
6284             return FALSE;
6285         }
6286         if (white_piece && !WhiteOnMove(currentMove)) {
6287             DisplayMoveError(_("It is Black's turn"));
6288             return FALSE;
6289         }
6290         break;
6291
6292       default:
6293       case IcsExamining:
6294         break;
6295     }
6296     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6297         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6298         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6299         && gameMode != AnalyzeFile && gameMode != Training) {
6300         DisplayMoveError(_("Displayed position is not current"));
6301         return FALSE;
6302     }
6303     return TRUE;
6304 }
6305
6306 Boolean
6307 OnlyMove (int *x, int *y, Boolean captures) 
6308 {
6309     DisambiguateClosure cl;
6310     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6311     switch(gameMode) {
6312       case MachinePlaysBlack:
6313       case IcsPlayingWhite:
6314       case BeginningOfGame:
6315         if(!WhiteOnMove(currentMove)) return FALSE;
6316         break;
6317       case MachinePlaysWhite:
6318       case IcsPlayingBlack:
6319         if(WhiteOnMove(currentMove)) return FALSE;
6320         break;
6321       case EditGame:
6322         break;
6323       default:
6324         return FALSE;
6325     }
6326     cl.pieceIn = EmptySquare;
6327     cl.rfIn = *y;
6328     cl.ffIn = *x;
6329     cl.rtIn = -1;
6330     cl.ftIn = -1;
6331     cl.promoCharIn = NULLCHAR;
6332     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6333     if( cl.kind == NormalMove ||
6334         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6335         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6336         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6337       fromX = cl.ff;
6338       fromY = cl.rf;
6339       *x = cl.ft;
6340       *y = cl.rt;
6341       return TRUE;
6342     }
6343     if(cl.kind != ImpossibleMove) return FALSE;
6344     cl.pieceIn = EmptySquare;
6345     cl.rfIn = -1;
6346     cl.ffIn = -1;
6347     cl.rtIn = *y;
6348     cl.ftIn = *x;
6349     cl.promoCharIn = NULLCHAR;
6350     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6351     if( cl.kind == NormalMove ||
6352         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6353         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6354         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6355       fromX = cl.ff;
6356       fromY = cl.rf;
6357       *x = cl.ft;
6358       *y = cl.rt;
6359       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6360       return TRUE;
6361     }
6362     return FALSE;
6363 }
6364
6365 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6366 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6367 int lastLoadGameUseList = FALSE;
6368 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6369 ChessMove lastLoadGameStart = EndOfFile;
6370
6371 void
6372 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6373 {
6374     ChessMove moveType;
6375     ChessSquare pdown, pup;
6376
6377     /* Check if the user is playing in turn.  This is complicated because we
6378        let the user "pick up" a piece before it is his turn.  So the piece he
6379        tried to pick up may have been captured by the time he puts it down!
6380        Therefore we use the color the user is supposed to be playing in this
6381        test, not the color of the piece that is currently on the starting
6382        square---except in EditGame mode, where the user is playing both
6383        sides; fortunately there the capture race can't happen.  (It can
6384        now happen in IcsExamining mode, but that's just too bad.  The user
6385        will get a somewhat confusing message in that case.)
6386        */
6387
6388     switch (gameMode) {
6389       case AnalyzeFile:
6390       case TwoMachinesPlay:
6391       case EndOfGame:
6392       case IcsObserving:
6393       case IcsIdle:
6394         /* We switched into a game mode where moves are not accepted,
6395            perhaps while the mouse button was down. */
6396         return;
6397
6398       case MachinePlaysWhite:
6399         /* User is moving for Black */
6400         if (WhiteOnMove(currentMove)) {
6401             DisplayMoveError(_("It is White's turn"));
6402             return;
6403         }
6404         break;
6405
6406       case MachinePlaysBlack:
6407         /* User is moving for White */
6408         if (!WhiteOnMove(currentMove)) {
6409             DisplayMoveError(_("It is Black's turn"));
6410             return;
6411         }
6412         break;
6413
6414       case PlayFromGameFile:
6415             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6416       case EditGame:
6417       case IcsExamining:
6418       case BeginningOfGame:
6419       case AnalyzeMode:
6420       case Training:
6421         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6422         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6423             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6424             /* User is moving for Black */
6425             if (WhiteOnMove(currentMove)) {
6426                 DisplayMoveError(_("It is White's turn"));
6427                 return;
6428             }
6429         } else {
6430             /* User is moving for White */
6431             if (!WhiteOnMove(currentMove)) {
6432                 DisplayMoveError(_("It is Black's turn"));
6433                 return;
6434             }
6435         }
6436         break;
6437
6438       case IcsPlayingBlack:
6439         /* User is moving for Black */
6440         if (WhiteOnMove(currentMove)) {
6441             if (!appData.premove) {
6442                 DisplayMoveError(_("It is White's turn"));
6443             } else if (toX >= 0 && toY >= 0) {
6444                 premoveToX = toX;
6445                 premoveToY = toY;
6446                 premoveFromX = fromX;
6447                 premoveFromY = fromY;
6448                 premovePromoChar = promoChar;
6449                 gotPremove = 1;
6450                 if (appData.debugMode)
6451                     fprintf(debugFP, "Got premove: fromX %d,"
6452                             "fromY %d, toX %d, toY %d\n",
6453                             fromX, fromY, toX, toY);
6454             }
6455             return;
6456         }
6457         break;
6458
6459       case IcsPlayingWhite:
6460         /* User is moving for White */
6461         if (!WhiteOnMove(currentMove)) {
6462             if (!appData.premove) {
6463                 DisplayMoveError(_("It is Black's turn"));
6464             } else if (toX >= 0 && toY >= 0) {
6465                 premoveToX = toX;
6466                 premoveToY = toY;
6467                 premoveFromX = fromX;
6468                 premoveFromY = fromY;
6469                 premovePromoChar = promoChar;
6470                 gotPremove = 1;
6471                 if (appData.debugMode)
6472                     fprintf(debugFP, "Got premove: fromX %d,"
6473                             "fromY %d, toX %d, toY %d\n",
6474                             fromX, fromY, toX, toY);
6475             }
6476             return;
6477         }
6478         break;
6479
6480       default:
6481         break;
6482
6483       case EditPosition:
6484         /* EditPosition, empty square, or different color piece;
6485            click-click move is possible */
6486         if (toX == -2 || toY == -2) {
6487             boards[0][fromY][fromX] = EmptySquare;
6488             DrawPosition(FALSE, boards[currentMove]);
6489             return;
6490         } else if (toX >= 0 && toY >= 0) {
6491             boards[0][toY][toX] = boards[0][fromY][fromX];
6492             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6493                 if(boards[0][fromY][0] != EmptySquare) {
6494                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6495                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6496                 }
6497             } else
6498             if(fromX == BOARD_RGHT+1) {
6499                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6500                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6501                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6502                 }
6503             } else
6504             boards[0][fromY][fromX] = EmptySquare;
6505             DrawPosition(FALSE, boards[currentMove]);
6506             return;
6507         }
6508         return;
6509     }
6510
6511     if(toX < 0 || toY < 0) return;
6512     pdown = boards[currentMove][fromY][fromX];
6513     pup = boards[currentMove][toY][toX];
6514
6515     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6516     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6517          if( pup != EmptySquare ) return;
6518          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6519            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6520                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6521            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6522            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6523            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6524            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6525          fromY = DROP_RANK;
6526     }
6527
6528     /* [HGM] always test for legality, to get promotion info */
6529     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6530                                          fromY, fromX, toY, toX, promoChar);
6531
6532     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6533
6534     /* [HGM] but possibly ignore an IllegalMove result */
6535     if (appData.testLegality) {
6536         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6537             DisplayMoveError(_("Illegal move"));
6538             return;
6539         }
6540     }
6541
6542     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6543 }
6544
6545 /* Common tail of UserMoveEvent and DropMenuEvent */
6546 int
6547 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6548 {
6549     char *bookHit = 0;
6550
6551     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6552         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6553         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6554         if(WhiteOnMove(currentMove)) {
6555             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6556         } else {
6557             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6558         }
6559     }
6560
6561     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6562        move type in caller when we know the move is a legal promotion */
6563     if(moveType == NormalMove && promoChar)
6564         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6565
6566     /* [HGM] <popupFix> The following if has been moved here from
6567        UserMoveEvent(). Because it seemed to belong here (why not allow
6568        piece drops in training games?), and because it can only be
6569        performed after it is known to what we promote. */
6570     if (gameMode == Training) {
6571       /* compare the move played on the board to the next move in the
6572        * game. If they match, display the move and the opponent's response.
6573        * If they don't match, display an error message.
6574        */
6575       int saveAnimate;
6576       Board testBoard;
6577       CopyBoard(testBoard, boards[currentMove]);
6578       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6579
6580       if (CompareBoards(testBoard, boards[currentMove+1])) {
6581         ForwardInner(currentMove+1);
6582
6583         /* Autoplay the opponent's response.
6584          * if appData.animate was TRUE when Training mode was entered,
6585          * the response will be animated.
6586          */
6587         saveAnimate = appData.animate;
6588         appData.animate = animateTraining;
6589         ForwardInner(currentMove+1);
6590         appData.animate = saveAnimate;
6591
6592         /* check for the end of the game */
6593         if (currentMove >= forwardMostMove) {
6594           gameMode = PlayFromGameFile;
6595           ModeHighlight();
6596           SetTrainingModeOff();
6597           DisplayInformation(_("End of game"));
6598         }
6599       } else {
6600         DisplayError(_("Incorrect move"), 0);
6601       }
6602       return 1;
6603     }
6604
6605   /* Ok, now we know that the move is good, so we can kill
6606      the previous line in Analysis Mode */
6607   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6608                                 && currentMove < forwardMostMove) {
6609     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6610     else forwardMostMove = currentMove;
6611   }
6612
6613   /* If we need the chess program but it's dead, restart it */
6614   ResurrectChessProgram();
6615
6616   /* A user move restarts a paused game*/
6617   if (pausing)
6618     PauseEvent();
6619
6620   thinkOutput[0] = NULLCHAR;
6621
6622   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6623
6624   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6625     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6626     return 1;
6627   }
6628
6629   if (gameMode == BeginningOfGame) {
6630     if (appData.noChessProgram) {
6631       gameMode = EditGame;
6632       SetGameInfo();
6633     } else {
6634       char buf[MSG_SIZ];
6635       gameMode = MachinePlaysBlack;
6636       StartClocks();
6637       SetGameInfo();
6638       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6639       DisplayTitle(buf);
6640       if (first.sendName) {
6641         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6642         SendToProgram(buf, &first);
6643       }
6644       StartClocks();
6645     }
6646     ModeHighlight();
6647   }
6648
6649   /* Relay move to ICS or chess engine */
6650   if (appData.icsActive) {
6651     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6652         gameMode == IcsExamining) {
6653       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6654         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6655         SendToICS("draw ");
6656         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6657       }
6658       // also send plain move, in case ICS does not understand atomic claims
6659       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6660       ics_user_moved = 1;
6661     }
6662   } else {
6663     if (first.sendTime && (gameMode == BeginningOfGame ||
6664                            gameMode == MachinePlaysWhite ||
6665                            gameMode == MachinePlaysBlack)) {
6666       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6667     }
6668     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6669          // [HGM] book: if program might be playing, let it use book
6670         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6671         first.maybeThinking = TRUE;
6672     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6673         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6674         SendBoard(&first, currentMove+1);
6675     } else SendMoveToProgram(forwardMostMove-1, &first);
6676     if (currentMove == cmailOldMove + 1) {
6677       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6678     }
6679   }
6680
6681   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6682
6683   switch (gameMode) {
6684   case EditGame:
6685     if(appData.testLegality)
6686     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6687     case MT_NONE:
6688     case MT_CHECK:
6689       break;
6690     case MT_CHECKMATE:
6691     case MT_STAINMATE:
6692       if (WhiteOnMove(currentMove)) {
6693         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6694       } else {
6695         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6696       }
6697       break;
6698     case MT_STALEMATE:
6699       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6700       break;
6701     }
6702     break;
6703
6704   case MachinePlaysBlack:
6705   case MachinePlaysWhite:
6706     /* disable certain menu options while machine is thinking */
6707     SetMachineThinkingEnables();
6708     break;
6709
6710   default:
6711     break;
6712   }
6713
6714   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6715   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6716
6717   if(bookHit) { // [HGM] book: simulate book reply
6718         static char bookMove[MSG_SIZ]; // a bit generous?
6719
6720         programStats.nodes = programStats.depth = programStats.time =
6721         programStats.score = programStats.got_only_move = 0;
6722         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6723
6724         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6725         strcat(bookMove, bookHit);
6726         HandleMachineMove(bookMove, &first);
6727   }
6728   return 1;
6729 }
6730
6731 void
6732 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6733 {
6734     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6735     Markers *m = (Markers *) closure;
6736     if(rf == fromY && ff == fromX)
6737         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6738                          || kind == WhiteCapturesEnPassant
6739                          || kind == BlackCapturesEnPassant);
6740     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6741 }
6742
6743 void
6744 MarkTargetSquares (int clear)
6745 {
6746   int x, y;
6747   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6748      !appData.testLegality || gameMode == EditPosition) return;
6749   if(clear) {
6750     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6751   } else {
6752     int capt = 0;
6753     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6754     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6755       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6756       if(capt)
6757       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6758     }
6759   }
6760   DrawPosition(TRUE, NULL);
6761 }
6762
6763 int
6764 Explode (Board board, int fromX, int fromY, int toX, int toY)
6765 {
6766     if(gameInfo.variant == VariantAtomic &&
6767        (board[toY][toX] != EmptySquare ||                     // capture?
6768         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6769                          board[fromY][fromX] == BlackPawn   )
6770       )) {
6771         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6772         return TRUE;
6773     }
6774     return FALSE;
6775 }
6776
6777 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6778
6779 int
6780 CanPromote (ChessSquare piece, int y)
6781 {
6782         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6783         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6784         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6785            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6786            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6787                                                   gameInfo.variant == VariantMakruk) return FALSE;
6788         return (piece == BlackPawn && y == 1 ||
6789                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6790                 piece == BlackLance && y == 1 ||
6791                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6792 }
6793
6794 void
6795 LeftClick (ClickType clickType, int xPix, int yPix)
6796 {
6797     int x, y;
6798     Boolean saveAnimate;
6799     static int second = 0, promotionChoice = 0, clearFlag = 0;
6800     char promoChoice = NULLCHAR;
6801     ChessSquare piece;
6802
6803     if(appData.seekGraph && appData.icsActive && loggedOn &&
6804         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6805         SeekGraphClick(clickType, xPix, yPix, 0);
6806         return;
6807     }
6808
6809     if (clickType == Press) ErrorPopDown();
6810
6811     x = EventToSquare(xPix, BOARD_WIDTH);
6812     y = EventToSquare(yPix, BOARD_HEIGHT);
6813     if (!flipView && y >= 0) {
6814         y = BOARD_HEIGHT - 1 - y;
6815     }
6816     if (flipView && x >= 0) {
6817         x = BOARD_WIDTH - 1 - x;
6818     }
6819
6820     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6821         defaultPromoChoice = promoSweep;
6822         promoSweep = EmptySquare;   // terminate sweep
6823         promoDefaultAltered = TRUE;
6824         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6825     }
6826
6827     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6828         if(clickType == Release) return; // ignore upclick of click-click destination
6829         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6830         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6831         if(gameInfo.holdingsWidth &&
6832                 (WhiteOnMove(currentMove)
6833                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6834                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6835             // click in right holdings, for determining promotion piece
6836             ChessSquare p = boards[currentMove][y][x];
6837             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6838             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6839             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6840                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6841                 fromX = fromY = -1;
6842                 return;
6843             }
6844         }
6845         DrawPosition(FALSE, boards[currentMove]);
6846         return;
6847     }
6848
6849     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6850     if(clickType == Press
6851             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6852               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6853               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6854         return;
6855
6856     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6857         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6858
6859     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6860         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6861                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6862         defaultPromoChoice = DefaultPromoChoice(side);
6863     }
6864
6865     autoQueen = appData.alwaysPromoteToQueen;
6866
6867     if (fromX == -1) {
6868       int originalY = y;
6869       gatingPiece = EmptySquare;
6870       if (clickType != Press) {
6871         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6872             DragPieceEnd(xPix, yPix); dragging = 0;
6873             DrawPosition(FALSE, NULL);
6874         }
6875         return;
6876       }
6877       fromX = x; fromY = y; toX = toY = -1;
6878       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6879          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6880          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6881             /* First square */
6882             if (OKToStartUserMove(fromX, fromY)) {
6883                 second = 0;
6884                 MarkTargetSquares(0);
6885                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6886                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6887                     promoSweep = defaultPromoChoice;
6888                     selectFlag = 0; lastX = xPix; lastY = yPix;
6889                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6890                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6891                 }
6892                 if (appData.highlightDragging) {
6893                     SetHighlights(fromX, fromY, -1, -1);
6894                 }
6895             } else fromX = fromY = -1;
6896             return;
6897         }
6898     }
6899
6900     /* fromX != -1 */
6901     if (clickType == Press && gameMode != EditPosition) {
6902         ChessSquare fromP;
6903         ChessSquare toP;
6904         int frc;
6905
6906         // ignore off-board to clicks
6907         if(y < 0 || x < 0) return;
6908
6909         /* Check if clicking again on the same color piece */
6910         fromP = boards[currentMove][fromY][fromX];
6911         toP = boards[currentMove][y][x];
6912         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6913         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6914              WhitePawn <= toP && toP <= WhiteKing &&
6915              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6916              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6917             (BlackPawn <= fromP && fromP <= BlackKing &&
6918              BlackPawn <= toP && toP <= BlackKing &&
6919              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6920              !(fromP == BlackKing && toP == BlackRook && frc))) {
6921             /* Clicked again on same color piece -- changed his mind */
6922             second = (x == fromX && y == fromY);
6923             promoDefaultAltered = FALSE;
6924             MarkTargetSquares(1);
6925            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6926             if (appData.highlightDragging) {
6927                 SetHighlights(x, y, -1, -1);
6928             } else {
6929                 ClearHighlights();
6930             }
6931             if (OKToStartUserMove(x, y)) {
6932                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6933                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6934                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6935                  gatingPiece = boards[currentMove][fromY][fromX];
6936                 else gatingPiece = EmptySquare;
6937                 fromX = x;
6938                 fromY = y; dragging = 1;
6939                 MarkTargetSquares(0);
6940                 DragPieceBegin(xPix, yPix, FALSE);
6941                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6942                     promoSweep = defaultPromoChoice;
6943                     selectFlag = 0; lastX = xPix; lastY = yPix;
6944                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6945                 }
6946             }
6947            }
6948            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6949            second = FALSE; 
6950         }
6951         // ignore clicks on holdings
6952         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6953     }
6954
6955     if (clickType == Release && x == fromX && y == fromY) {
6956         DragPieceEnd(xPix, yPix); dragging = 0;
6957         if(clearFlag) {
6958             // a deferred attempt to click-click move an empty square on top of a piece
6959             boards[currentMove][y][x] = EmptySquare;
6960             ClearHighlights();
6961             DrawPosition(FALSE, boards[currentMove]);
6962             fromX = fromY = -1; clearFlag = 0;
6963             return;
6964         }
6965         if (appData.animateDragging) {
6966             /* Undo animation damage if any */
6967             DrawPosition(FALSE, NULL);
6968         }
6969         if (second) {
6970             /* Second up/down in same square; just abort move */
6971             second = 0;
6972             fromX = fromY = -1;
6973             gatingPiece = EmptySquare;
6974             ClearHighlights();
6975             gotPremove = 0;
6976             ClearPremoveHighlights();
6977         } else {
6978             /* First upclick in same square; start click-click mode */
6979             SetHighlights(x, y, -1, -1);
6980         }
6981         return;
6982     }
6983
6984     clearFlag = 0;
6985
6986     /* we now have a different from- and (possibly off-board) to-square */
6987     /* Completed move */
6988     toX = x;
6989     toY = y;
6990     saveAnimate = appData.animate;
6991     MarkTargetSquares(1);
6992     if (clickType == Press) {
6993         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6994             // must be Edit Position mode with empty-square selected
6995             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
6996             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6997             return;
6998         }
6999         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7000             ChessSquare piece = boards[currentMove][fromY][fromX];
7001             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7002             promoSweep = defaultPromoChoice;
7003             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7004             selectFlag = 0; lastX = xPix; lastY = yPix;
7005             Sweep(0); // Pawn that is going to promote: preview promotion piece
7006             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7007             DrawPosition(FALSE, boards[currentMove]);
7008             return;
7009         }
7010         /* Finish clickclick move */
7011         if (appData.animate || appData.highlightLastMove) {
7012             SetHighlights(fromX, fromY, toX, toY);
7013         } else {
7014             ClearHighlights();
7015         }
7016     } else {
7017         /* Finish drag move */
7018         if (appData.highlightLastMove) {
7019             SetHighlights(fromX, fromY, toX, toY);
7020         } else {
7021             ClearHighlights();
7022         }
7023         DragPieceEnd(xPix, yPix); dragging = 0;
7024         /* Don't animate move and drag both */
7025         appData.animate = FALSE;
7026     }
7027
7028     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7029     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7030         ChessSquare piece = boards[currentMove][fromY][fromX];
7031         if(gameMode == EditPosition && piece != EmptySquare &&
7032            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7033             int n;
7034
7035             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7036                 n = PieceToNumber(piece - (int)BlackPawn);
7037                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7038                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7039                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7040             } else
7041             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7042                 n = PieceToNumber(piece);
7043                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7044                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7045                 boards[currentMove][n][BOARD_WIDTH-2]++;
7046             }
7047             boards[currentMove][fromY][fromX] = EmptySquare;
7048         }
7049         ClearHighlights();
7050         fromX = fromY = -1;
7051         DrawPosition(TRUE, boards[currentMove]);
7052         return;
7053     }
7054
7055     // off-board moves should not be highlighted
7056     if(x < 0 || y < 0) ClearHighlights();
7057
7058     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7059
7060     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7061         SetHighlights(fromX, fromY, toX, toY);
7062         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7063             // [HGM] super: promotion to captured piece selected from holdings
7064             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7065             promotionChoice = TRUE;
7066             // kludge follows to temporarily execute move on display, without promoting yet
7067             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7068             boards[currentMove][toY][toX] = p;
7069             DrawPosition(FALSE, boards[currentMove]);
7070             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7071             boards[currentMove][toY][toX] = q;
7072             DisplayMessage("Click in holdings to choose piece", "");
7073             return;
7074         }
7075         PromotionPopUp();
7076     } else {
7077         int oldMove = currentMove;
7078         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7079         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7080         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7081         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7082            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7083             DrawPosition(TRUE, boards[currentMove]);
7084         fromX = fromY = -1;
7085     }
7086     appData.animate = saveAnimate;
7087     if (appData.animate || appData.animateDragging) {
7088         /* Undo animation damage if needed */
7089         DrawPosition(FALSE, NULL);
7090     }
7091 }
7092
7093 int
7094 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7095 {   // front-end-free part taken out of PieceMenuPopup
7096     int whichMenu; int xSqr, ySqr;
7097
7098     if(seekGraphUp) { // [HGM] seekgraph
7099         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7100         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7101         return -2;
7102     }
7103
7104     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7105          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7106         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7107         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7108         if(action == Press)   {
7109             originalFlip = flipView;
7110             flipView = !flipView; // temporarily flip board to see game from partners perspective
7111             DrawPosition(TRUE, partnerBoard);
7112             DisplayMessage(partnerStatus, "");
7113             partnerUp = TRUE;
7114         } else if(action == Release) {
7115             flipView = originalFlip;
7116             DrawPosition(TRUE, boards[currentMove]);
7117             partnerUp = FALSE;
7118         }
7119         return -2;
7120     }
7121
7122     xSqr = EventToSquare(x, BOARD_WIDTH);
7123     ySqr = EventToSquare(y, BOARD_HEIGHT);
7124     if (action == Release) {
7125         if(pieceSweep != EmptySquare) {
7126             EditPositionMenuEvent(pieceSweep, toX, toY);
7127             pieceSweep = EmptySquare;
7128         } else UnLoadPV(); // [HGM] pv
7129     }
7130     if (action != Press) return -2; // return code to be ignored
7131     switch (gameMode) {
7132       case IcsExamining:
7133         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7134       case EditPosition:
7135         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7136         if (xSqr < 0 || ySqr < 0) return -1;
7137         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7138         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7139         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7140         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7141         NextPiece(0);
7142         return 2; // grab
7143       case IcsObserving:
7144         if(!appData.icsEngineAnalyze) return -1;
7145       case IcsPlayingWhite:
7146       case IcsPlayingBlack:
7147         if(!appData.zippyPlay) goto noZip;
7148       case AnalyzeMode:
7149       case AnalyzeFile:
7150       case MachinePlaysWhite:
7151       case MachinePlaysBlack:
7152       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7153         if (!appData.dropMenu) {
7154           LoadPV(x, y);
7155           return 2; // flag front-end to grab mouse events
7156         }
7157         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7158            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7159       case EditGame:
7160       noZip:
7161         if (xSqr < 0 || ySqr < 0) return -1;
7162         if (!appData.dropMenu || appData.testLegality &&
7163             gameInfo.variant != VariantBughouse &&
7164             gameInfo.variant != VariantCrazyhouse) return -1;
7165         whichMenu = 1; // drop menu
7166         break;
7167       default:
7168         return -1;
7169     }
7170
7171     if (((*fromX = xSqr) < 0) ||
7172         ((*fromY = ySqr) < 0)) {
7173         *fromX = *fromY = -1;
7174         return -1;
7175     }
7176     if (flipView)
7177       *fromX = BOARD_WIDTH - 1 - *fromX;
7178     else
7179       *fromY = BOARD_HEIGHT - 1 - *fromY;
7180
7181     return whichMenu;
7182 }
7183
7184 void
7185 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7186 {
7187 //    char * hint = lastHint;
7188     FrontEndProgramStats stats;
7189
7190     stats.which = cps == &first ? 0 : 1;
7191     stats.depth = cpstats->depth;
7192     stats.nodes = cpstats->nodes;
7193     stats.score = cpstats->score;
7194     stats.time = cpstats->time;
7195     stats.pv = cpstats->movelist;
7196     stats.hint = lastHint;
7197     stats.an_move_index = 0;
7198     stats.an_move_count = 0;
7199
7200     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7201         stats.hint = cpstats->move_name;
7202         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7203         stats.an_move_count = cpstats->nr_moves;
7204     }
7205
7206     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
7207
7208     SetProgramStats( &stats );
7209 }
7210
7211 void
7212 ClearEngineOutputPane (int which)
7213 {
7214     static FrontEndProgramStats dummyStats;
7215     dummyStats.which = which;
7216     dummyStats.pv = "#";
7217     SetProgramStats( &dummyStats );
7218 }
7219
7220 #define MAXPLAYERS 500
7221
7222 char *
7223 TourneyStandings (int display)
7224 {
7225     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7226     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7227     char result, *p, *names[MAXPLAYERS];
7228
7229     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7230         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7231     names[0] = p = strdup(appData.participants);
7232     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7233
7234     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7235
7236     while(result = appData.results[nr]) {
7237         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7238         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7239         wScore = bScore = 0;
7240         switch(result) {
7241           case '+': wScore = 2; break;
7242           case '-': bScore = 2; break;
7243           case '=': wScore = bScore = 1; break;
7244           case ' ':
7245           case '*': return strdup("busy"); // tourney not finished
7246         }
7247         score[w] += wScore;
7248         score[b] += bScore;
7249         games[w]++;
7250         games[b]++;
7251         nr++;
7252     }
7253     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7254     for(w=0; w<nPlayers; w++) {
7255         bScore = -1;
7256         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7257         ranking[w] = b; points[w] = bScore; score[b] = -2;
7258     }
7259     p = malloc(nPlayers*34+1);
7260     for(w=0; w<nPlayers && w<display; w++)
7261         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7262     free(names[0]);
7263     return p;
7264 }
7265
7266 void
7267 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7268 {       // count all piece types
7269         int p, f, r;
7270         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7271         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7272         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7273                 p = board[r][f];
7274                 pCnt[p]++;
7275                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7276                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7277                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7278                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7279                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7280                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7281         }
7282 }
7283
7284 int
7285 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7286 {
7287         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7288         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7289
7290         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7291         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7292         if(myPawns == 2 && nMine == 3) // KPP
7293             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7294         if(myPawns == 1 && nMine == 2) // KP
7295             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7296         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7297             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7298         if(myPawns) return FALSE;
7299         if(pCnt[WhiteRook+side])
7300             return pCnt[BlackRook-side] ||
7301                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7302                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7303                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7304         if(pCnt[WhiteCannon+side]) {
7305             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7306             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7307         }
7308         if(pCnt[WhiteKnight+side])
7309             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7310         return FALSE;
7311 }
7312
7313 int
7314 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7315 {
7316         VariantClass v = gameInfo.variant;
7317
7318         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7319         if(v == VariantShatranj) return TRUE; // always winnable through baring
7320         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7321         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7322
7323         if(v == VariantXiangqi) {
7324                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7325
7326                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7327                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7328                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7329                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7330                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7331                 if(stale) // we have at least one last-rank P plus perhaps C
7332                     return majors // KPKX
7333                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7334                 else // KCA*E*
7335                     return pCnt[WhiteFerz+side] // KCAK
7336                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7337                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7338                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7339
7340         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7341                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7342
7343                 if(nMine == 1) return FALSE; // bare King
7344                 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
7345                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7346                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7347                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7348                 if(pCnt[WhiteKnight+side])
7349                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7350                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7351                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7352                 if(nBishops)
7353                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7354                 if(pCnt[WhiteAlfil+side])
7355                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7356                 if(pCnt[WhiteWazir+side])
7357                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7358         }
7359
7360         return TRUE;
7361 }
7362
7363 int
7364 CompareWithRights (Board b1, Board b2)
7365 {
7366     int rights = 0;
7367     if(!CompareBoards(b1, b2)) return FALSE;
7368     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7369     /* compare castling rights */
7370     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7371            rights++; /* King lost rights, while rook still had them */
7372     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7373         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7374            rights++; /* but at least one rook lost them */
7375     }
7376     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7377            rights++;
7378     if( b1[CASTLING][5] != NoRights ) {
7379         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7380            rights++;
7381     }
7382     return rights == 0;
7383 }
7384
7385 int
7386 Adjudicate (ChessProgramState *cps)
7387 {       // [HGM] some adjudications useful with buggy engines
7388         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7389         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7390         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7391         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7392         int k, count = 0; static int bare = 1;
7393         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7394         Boolean canAdjudicate = !appData.icsActive;
7395
7396         // most tests only when we understand the game, i.e. legality-checking on
7397             if( appData.testLegality )
7398             {   /* [HGM] Some more adjudications for obstinate engines */
7399                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7400                 static int moveCount = 6;
7401                 ChessMove result;
7402                 char *reason = NULL;
7403
7404                 /* Count what is on board. */
7405                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7406
7407                 /* Some material-based adjudications that have to be made before stalemate test */
7408                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7409                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7410                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7411                      if(canAdjudicate && appData.checkMates) {
7412                          if(engineOpponent)
7413                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7414                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7415                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7416                          return 1;
7417                      }
7418                 }
7419
7420                 /* Bare King in Shatranj (loses) or Losers (wins) */
7421                 if( nrW == 1 || nrB == 1) {
7422                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7423                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7424                      if(canAdjudicate && appData.checkMates) {
7425                          if(engineOpponent)
7426                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7427                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7428                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7429                          return 1;
7430                      }
7431                   } else
7432                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7433                   {    /* bare King */
7434                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7435                         if(canAdjudicate && appData.checkMates) {
7436                             /* but only adjudicate if adjudication enabled */
7437                             if(engineOpponent)
7438                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7439                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7440                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7441                             return 1;
7442                         }
7443                   }
7444                 } else bare = 1;
7445
7446
7447             // don't wait for engine to announce game end if we can judge ourselves
7448             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7449               case MT_CHECK:
7450                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7451                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7452                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7453                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7454                             checkCnt++;
7455                         if(checkCnt >= 2) {
7456                             reason = "Xboard adjudication: 3rd check";
7457                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7458                             break;
7459                         }
7460                     }
7461                 }
7462               case MT_NONE:
7463               default:
7464                 break;
7465               case MT_STALEMATE:
7466               case MT_STAINMATE:
7467                 reason = "Xboard adjudication: Stalemate";
7468                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7469                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7470                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7471                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7472                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7473                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7474                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7475                                                                         EP_CHECKMATE : EP_WINS);
7476                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7477                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7478                 }
7479                 break;
7480               case MT_CHECKMATE:
7481                 reason = "Xboard adjudication: Checkmate";
7482                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7483                 break;
7484             }
7485
7486                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7487                     case EP_STALEMATE:
7488                         result = GameIsDrawn; break;
7489                     case EP_CHECKMATE:
7490                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7491                     case EP_WINS:
7492                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7493                     default:
7494                         result = EndOfFile;
7495                 }
7496                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7497                     if(engineOpponent)
7498                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7499                     GameEnds( result, reason, GE_XBOARD );
7500                     return 1;
7501                 }
7502
7503                 /* Next absolutely insufficient mating material. */
7504                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7505                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7506                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7507
7508                      /* always flag draws, for judging claims */
7509                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7510
7511                      if(canAdjudicate && appData.materialDraws) {
7512                          /* but only adjudicate them if adjudication enabled */
7513                          if(engineOpponent) {
7514                            SendToProgram("force\n", engineOpponent); // suppress reply
7515                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7516                          }
7517                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7518                          return 1;
7519                      }
7520                 }
7521
7522                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7523                 if(gameInfo.variant == VariantXiangqi ?
7524                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7525                  : nrW + nrB == 4 &&
7526                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7527                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7528                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7529                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7530                    ) ) {
7531                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7532                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7533                           if(engineOpponent) {
7534                             SendToProgram("force\n", engineOpponent); // suppress reply
7535                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7536                           }
7537                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7538                           return 1;
7539                      }
7540                 } else moveCount = 6;
7541             }
7542         if (appData.debugMode) { int i;
7543             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7544                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7545                     appData.drawRepeats);
7546             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7547               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7548
7549         }
7550
7551         // Repetition draws and 50-move rule can be applied independently of legality testing
7552
7553                 /* Check for rep-draws */
7554                 count = 0;
7555                 for(k = forwardMostMove-2;
7556                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7557                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7558                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7559                     k-=2)
7560                 {   int rights=0;
7561                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7562                         /* compare castling rights */
7563                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7564                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7565                                 rights++; /* King lost rights, while rook still had them */
7566                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7567                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7568                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7569                                    rights++; /* but at least one rook lost them */
7570                         }
7571                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7572                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7573                                 rights++;
7574                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7575                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7576                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7577                                    rights++;
7578                         }
7579                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7580                             && appData.drawRepeats > 1) {
7581                              /* adjudicate after user-specified nr of repeats */
7582                              int result = GameIsDrawn;
7583                              char *details = "XBoard adjudication: repetition draw";
7584                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7585                                 // [HGM] xiangqi: check for forbidden perpetuals
7586                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7587                                 for(m=forwardMostMove; m>k; m-=2) {
7588                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7589                                         ourPerpetual = 0; // the current mover did not always check
7590                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7591                                         hisPerpetual = 0; // the opponent did not always check
7592                                 }
7593                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7594                                                                         ourPerpetual, hisPerpetual);
7595                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7596                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7597                                     details = "Xboard adjudication: perpetual checking";
7598                                 } else
7599                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7600                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7601                                 } else
7602                                 // Now check for perpetual chases
7603                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7604                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7605                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7606                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7607                                         static char resdet[MSG_SIZ];
7608                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7609                                         details = resdet;
7610                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7611                                     } else
7612                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7613                                         break; // Abort repetition-checking loop.
7614                                 }
7615                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7616                              }
7617                              if(engineOpponent) {
7618                                SendToProgram("force\n", engineOpponent); // suppress reply
7619                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7620                              }
7621                              GameEnds( result, details, GE_XBOARD );
7622                              return 1;
7623                         }
7624                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7625                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7626                     }
7627                 }
7628
7629                 /* Now we test for 50-move draws. Determine ply count */
7630                 count = forwardMostMove;
7631                 /* look for last irreversble move */
7632                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7633                     count--;
7634                 /* if we hit starting position, add initial plies */
7635                 if( count == backwardMostMove )
7636                     count -= initialRulePlies;
7637                 count = forwardMostMove - count;
7638                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7639                         // adjust reversible move counter for checks in Xiangqi
7640                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7641                         if(i < backwardMostMove) i = backwardMostMove;
7642                         while(i <= forwardMostMove) {
7643                                 lastCheck = inCheck; // check evasion does not count
7644                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7645                                 if(inCheck || lastCheck) count--; // check does not count
7646                                 i++;
7647                         }
7648                 }
7649                 if( count >= 100)
7650                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7651                          /* this is used to judge if draw claims are legal */
7652                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7653                          if(engineOpponent) {
7654                            SendToProgram("force\n", engineOpponent); // suppress reply
7655                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7656                          }
7657                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7658                          return 1;
7659                 }
7660
7661                 /* if draw offer is pending, treat it as a draw claim
7662                  * when draw condition present, to allow engines a way to
7663                  * claim draws before making their move to avoid a race
7664                  * condition occurring after their move
7665                  */
7666                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7667                          char *p = NULL;
7668                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7669                              p = "Draw claim: 50-move rule";
7670                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7671                              p = "Draw claim: 3-fold repetition";
7672                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7673                              p = "Draw claim: insufficient mating material";
7674                          if( p != NULL && canAdjudicate) {
7675                              if(engineOpponent) {
7676                                SendToProgram("force\n", engineOpponent); // suppress reply
7677                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7678                              }
7679                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7680                              return 1;
7681                          }
7682                 }
7683
7684                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7685                     if(engineOpponent) {
7686                       SendToProgram("force\n", engineOpponent); // suppress reply
7687                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7688                     }
7689                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7690                     return 1;
7691                 }
7692         return 0;
7693 }
7694
7695 char *
7696 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7697 {   // [HGM] book: this routine intercepts moves to simulate book replies
7698     char *bookHit = NULL;
7699
7700     //first determine if the incoming move brings opponent into his book
7701     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7702         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7703     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7704     if(bookHit != NULL && !cps->bookSuspend) {
7705         // make sure opponent is not going to reply after receiving move to book position
7706         SendToProgram("force\n", cps);
7707         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7708     }
7709     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7710     // now arrange restart after book miss
7711     if(bookHit) {
7712         // after a book hit we never send 'go', and the code after the call to this routine
7713         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7714         char buf[MSG_SIZ], *move = bookHit;
7715         if(cps->useSAN) {
7716             int fromX, fromY, toX, toY;
7717             char promoChar;
7718             ChessMove moveType;
7719             move = buf + 30;
7720             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7721                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7722                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7723                                     PosFlags(forwardMostMove),
7724                                     fromY, fromX, toY, toX, promoChar, move);
7725             } else {
7726                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7727                 bookHit = NULL;
7728             }
7729         }
7730         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7731         SendToProgram(buf, cps);
7732         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7733     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7734         SendToProgram("go\n", cps);
7735         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7736     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7737         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7738             SendToProgram("go\n", cps);
7739         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7740     }
7741     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7742 }
7743
7744 char *savedMessage;
7745 ChessProgramState *savedState;
7746 void
7747 DeferredBookMove (void)
7748 {
7749         if(savedState->lastPing != savedState->lastPong)
7750                     ScheduleDelayedEvent(DeferredBookMove, 10);
7751         else
7752         HandleMachineMove(savedMessage, savedState);
7753 }
7754
7755 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7756
7757 void
7758 HandleMachineMove (char *message, ChessProgramState *cps)
7759 {
7760     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7761     char realname[MSG_SIZ];
7762     int fromX, fromY, toX, toY;
7763     ChessMove moveType;
7764     char promoChar;
7765     char *p, *pv=buf1;
7766     int machineWhite;
7767     char *bookHit;
7768
7769     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7770         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7771         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7772             DisplayError(_("Invalid pairing from pairing engine"), 0);
7773             return;
7774         }
7775         pairingReceived = 1;
7776         NextMatchGame();
7777         return; // Skim the pairing messages here.
7778     }
7779
7780     cps->userError = 0;
7781
7782 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7783     /*
7784      * Kludge to ignore BEL characters
7785      */
7786     while (*message == '\007') message++;
7787
7788     /*
7789      * [HGM] engine debug message: ignore lines starting with '#' character
7790      */
7791     if(cps->debug && *message == '#') return;
7792
7793     /*
7794      * Look for book output
7795      */
7796     if (cps == &first && bookRequested) {
7797         if (message[0] == '\t' || message[0] == ' ') {
7798             /* Part of the book output is here; append it */
7799             strcat(bookOutput, message);
7800             strcat(bookOutput, "  \n");
7801             return;
7802         } else if (bookOutput[0] != NULLCHAR) {
7803             /* All of book output has arrived; display it */
7804             char *p = bookOutput;
7805             while (*p != NULLCHAR) {
7806                 if (*p == '\t') *p = ' ';
7807                 p++;
7808             }
7809             DisplayInformation(bookOutput);
7810             bookRequested = FALSE;
7811             /* Fall through to parse the current output */
7812         }
7813     }
7814
7815     /*
7816      * Look for machine move.
7817      */
7818     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7819         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7820     {
7821         /* This method is only useful on engines that support ping */
7822         if (cps->lastPing != cps->lastPong) {
7823           if (gameMode == BeginningOfGame) {
7824             /* Extra move from before last new; ignore */
7825             if (appData.debugMode) {
7826                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7827             }
7828           } else {
7829             if (appData.debugMode) {
7830                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7831                         cps->which, gameMode);
7832             }
7833
7834             SendToProgram("undo\n", cps);
7835           }
7836           return;
7837         }
7838
7839         switch (gameMode) {
7840           case BeginningOfGame:
7841             /* Extra move from before last reset; ignore */
7842             if (appData.debugMode) {
7843                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7844             }
7845             return;
7846
7847           case EndOfGame:
7848           case IcsIdle:
7849           default:
7850             /* Extra move after we tried to stop.  The mode test is
7851                not a reliable way of detecting this problem, but it's
7852                the best we can do on engines that don't support ping.
7853             */
7854             if (appData.debugMode) {
7855                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7856                         cps->which, gameMode);
7857             }
7858             SendToProgram("undo\n", cps);
7859             return;
7860
7861           case MachinePlaysWhite:
7862           case IcsPlayingWhite:
7863             machineWhite = TRUE;
7864             break;
7865
7866           case MachinePlaysBlack:
7867           case IcsPlayingBlack:
7868             machineWhite = FALSE;
7869             break;
7870
7871           case TwoMachinesPlay:
7872             machineWhite = (cps->twoMachinesColor[0] == 'w');
7873             break;
7874         }
7875         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7876             if (appData.debugMode) {
7877                 fprintf(debugFP,
7878                         "Ignoring move out of turn by %s, gameMode %d"
7879                         ", forwardMost %d\n",
7880                         cps->which, gameMode, forwardMostMove);
7881             }
7882             return;
7883         }
7884
7885     if (appData.debugMode) { int f = forwardMostMove;
7886         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7887                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7888                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7889     }
7890         if(cps->alphaRank) AlphaRank(machineMove, 4);
7891         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7892                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7893             /* Machine move could not be parsed; ignore it. */
7894           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7895                     machineMove, _(cps->which));
7896             DisplayError(buf1, 0);
7897             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7898                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7899             if (gameMode == TwoMachinesPlay) {
7900               GameEnds(machineWhite ? BlackWins : WhiteWins,
7901                        buf1, GE_XBOARD);
7902             }
7903             return;
7904         }
7905
7906         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7907         /* So we have to redo legality test with true e.p. status here,  */
7908         /* to make sure an illegal e.p. capture does not slip through,   */
7909         /* to cause a forfeit on a justified illegal-move complaint      */
7910         /* of the opponent.                                              */
7911         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7912            ChessMove moveType;
7913            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7914                              fromY, fromX, toY, toX, promoChar);
7915             if (appData.debugMode) {
7916                 int i;
7917                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7918                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7919                 fprintf(debugFP, "castling rights\n");
7920             }
7921             if(moveType == IllegalMove) {
7922               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7923                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7924                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7925                            buf1, GE_XBOARD);
7926                 return;
7927            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7928            /* [HGM] Kludge to handle engines that send FRC-style castling
7929               when they shouldn't (like TSCP-Gothic) */
7930            switch(moveType) {
7931              case WhiteASideCastleFR:
7932              case BlackASideCastleFR:
7933                toX+=2;
7934                currentMoveString[2]++;
7935                break;
7936              case WhiteHSideCastleFR:
7937              case BlackHSideCastleFR:
7938                toX--;
7939                currentMoveString[2]--;
7940                break;
7941              default: ; // nothing to do, but suppresses warning of pedantic compilers
7942            }
7943         }
7944         hintRequested = FALSE;
7945         lastHint[0] = NULLCHAR;
7946         bookRequested = FALSE;
7947         /* Program may be pondering now */
7948         cps->maybeThinking = TRUE;
7949         if (cps->sendTime == 2) cps->sendTime = 1;
7950         if (cps->offeredDraw) cps->offeredDraw--;
7951
7952         /* [AS] Save move info*/
7953         pvInfoList[ forwardMostMove ].score = programStats.score;
7954         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7955         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7956
7957         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7958
7959         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7960         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7961             int count = 0;
7962
7963             while( count < adjudicateLossPlies ) {
7964                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7965
7966                 if( count & 1 ) {
7967                     score = -score; /* Flip score for winning side */
7968                 }
7969
7970                 if( score > adjudicateLossThreshold ) {
7971                     break;
7972                 }
7973
7974                 count++;
7975             }
7976
7977             if( count >= adjudicateLossPlies ) {
7978                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7979
7980                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7981                     "Xboard adjudication",
7982                     GE_XBOARD );
7983
7984                 return;
7985             }
7986         }
7987
7988         if(Adjudicate(cps)) {
7989             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7990             return; // [HGM] adjudicate: for all automatic game ends
7991         }
7992
7993 #if ZIPPY
7994         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7995             first.initDone) {
7996           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7997                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7998                 SendToICS("draw ");
7999                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8000           }
8001           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8002           ics_user_moved = 1;
8003           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8004                 char buf[3*MSG_SIZ];
8005
8006                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8007                         programStats.score / 100.,
8008                         programStats.depth,
8009                         programStats.time / 100.,
8010                         (unsigned int)programStats.nodes,
8011                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8012                         programStats.movelist);
8013                 SendToICS(buf);
8014 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8015           }
8016         }
8017 #endif
8018
8019         /* [AS] Clear stats for next move */
8020         ClearProgramStats();
8021         thinkOutput[0] = NULLCHAR;
8022         hiddenThinkOutputState = 0;
8023
8024         bookHit = NULL;
8025         if (gameMode == TwoMachinesPlay) {
8026             /* [HGM] relaying draw offers moved to after reception of move */
8027             /* and interpreting offer as claim if it brings draw condition */
8028             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8029                 SendToProgram("draw\n", cps->other);
8030             }
8031             if (cps->other->sendTime) {
8032                 SendTimeRemaining(cps->other,
8033                                   cps->other->twoMachinesColor[0] == 'w');
8034             }
8035             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8036             if (firstMove && !bookHit) {
8037                 firstMove = FALSE;
8038                 if (cps->other->useColors) {
8039                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8040                 }
8041                 SendToProgram("go\n", cps->other);
8042             }
8043             cps->other->maybeThinking = TRUE;
8044         }
8045
8046         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8047
8048         if (!pausing && appData.ringBellAfterMoves) {
8049             RingBell();
8050         }
8051
8052         /*
8053          * Reenable menu items that were disabled while
8054          * machine was thinking
8055          */
8056         if (gameMode != TwoMachinesPlay)
8057             SetUserThinkingEnables();
8058
8059         // [HGM] book: after book hit opponent has received move and is now in force mode
8060         // force the book reply into it, and then fake that it outputted this move by jumping
8061         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8062         if(bookHit) {
8063                 static char bookMove[MSG_SIZ]; // a bit generous?
8064
8065                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8066                 strcat(bookMove, bookHit);
8067                 message = bookMove;
8068                 cps = cps->other;
8069                 programStats.nodes = programStats.depth = programStats.time =
8070                 programStats.score = programStats.got_only_move = 0;
8071                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8072
8073                 if(cps->lastPing != cps->lastPong) {
8074                     savedMessage = message; // args for deferred call
8075                     savedState = cps;
8076                     ScheduleDelayedEvent(DeferredBookMove, 10);
8077                     return;
8078                 }
8079                 goto FakeBookMove;
8080         }
8081
8082         return;
8083     }
8084
8085     /* Set special modes for chess engines.  Later something general
8086      *  could be added here; for now there is just one kludge feature,
8087      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8088      *  when "xboard" is given as an interactive command.
8089      */
8090     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8091         cps->useSigint = FALSE;
8092         cps->useSigterm = FALSE;
8093     }
8094     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8095       ParseFeatures(message+8, cps);
8096       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8097     }
8098
8099     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8100                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8101       int dummy, s=6; char buf[MSG_SIZ];
8102       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8103       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8104       if(startedFromSetupPosition) return;
8105       ParseFEN(boards[0], &dummy, message+s);
8106       DrawPosition(TRUE, boards[0]);
8107       startedFromSetupPosition = TRUE;
8108       return;
8109     }
8110     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8111      * want this, I was asked to put it in, and obliged.
8112      */
8113     if (!strncmp(message, "setboard ", 9)) {
8114         Board initial_position;
8115
8116         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8117
8118         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8119             DisplayError(_("Bad FEN received from engine"), 0);
8120             return ;
8121         } else {
8122            Reset(TRUE, FALSE);
8123            CopyBoard(boards[0], initial_position);
8124            initialRulePlies = FENrulePlies;
8125            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8126            else gameMode = MachinePlaysBlack;
8127            DrawPosition(FALSE, boards[currentMove]);
8128         }
8129         return;
8130     }
8131
8132     /*
8133      * Look for communication commands
8134      */
8135     if (!strncmp(message, "telluser ", 9)) {
8136         if(message[9] == '\\' && message[10] == '\\')
8137             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8138         PlayTellSound();
8139         DisplayNote(message + 9);
8140         return;
8141     }
8142     if (!strncmp(message, "tellusererror ", 14)) {
8143         cps->userError = 1;
8144         if(message[14] == '\\' && message[15] == '\\')
8145             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8146         PlayTellSound();
8147         DisplayError(message + 14, 0);
8148         return;
8149     }
8150     if (!strncmp(message, "tellopponent ", 13)) {
8151       if (appData.icsActive) {
8152         if (loggedOn) {
8153           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8154           SendToICS(buf1);
8155         }
8156       } else {
8157         DisplayNote(message + 13);
8158       }
8159       return;
8160     }
8161     if (!strncmp(message, "tellothers ", 11)) {
8162       if (appData.icsActive) {
8163         if (loggedOn) {
8164           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8165           SendToICS(buf1);
8166         }
8167       }
8168       return;
8169     }
8170     if (!strncmp(message, "tellall ", 8)) {
8171       if (appData.icsActive) {
8172         if (loggedOn) {
8173           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8174           SendToICS(buf1);
8175         }
8176       } else {
8177         DisplayNote(message + 8);
8178       }
8179       return;
8180     }
8181     if (strncmp(message, "warning", 7) == 0) {
8182         /* Undocumented feature, use tellusererror in new code */
8183         DisplayError(message, 0);
8184         return;
8185     }
8186     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8187         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8188         strcat(realname, " query");
8189         AskQuestion(realname, buf2, buf1, cps->pr);
8190         return;
8191     }
8192     /* Commands from the engine directly to ICS.  We don't allow these to be
8193      *  sent until we are logged on. Crafty kibitzes have been known to
8194      *  interfere with the login process.
8195      */
8196     if (loggedOn) {
8197         if (!strncmp(message, "tellics ", 8)) {
8198             SendToICS(message + 8);
8199             SendToICS("\n");
8200             return;
8201         }
8202         if (!strncmp(message, "tellicsnoalias ", 15)) {
8203             SendToICS(ics_prefix);
8204             SendToICS(message + 15);
8205             SendToICS("\n");
8206             return;
8207         }
8208         /* The following are for backward compatibility only */
8209         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8210             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8211             SendToICS(ics_prefix);
8212             SendToICS(message);
8213             SendToICS("\n");
8214             return;
8215         }
8216     }
8217     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8218         return;
8219     }
8220     /*
8221      * If the move is illegal, cancel it and redraw the board.
8222      * Also deal with other error cases.  Matching is rather loose
8223      * here to accommodate engines written before the spec.
8224      */
8225     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8226         strncmp(message, "Error", 5) == 0) {
8227         if (StrStr(message, "name") ||
8228             StrStr(message, "rating") || StrStr(message, "?") ||
8229             StrStr(message, "result") || StrStr(message, "board") ||
8230             StrStr(message, "bk") || StrStr(message, "computer") ||
8231             StrStr(message, "variant") || StrStr(message, "hint") ||
8232             StrStr(message, "random") || StrStr(message, "depth") ||
8233             StrStr(message, "accepted")) {
8234             return;
8235         }
8236         if (StrStr(message, "protover")) {
8237           /* Program is responding to input, so it's apparently done
8238              initializing, and this error message indicates it is
8239              protocol version 1.  So we don't need to wait any longer
8240              for it to initialize and send feature commands. */
8241           FeatureDone(cps, 1);
8242           cps->protocolVersion = 1;
8243           return;
8244         }
8245         cps->maybeThinking = FALSE;
8246
8247         if (StrStr(message, "draw")) {
8248             /* Program doesn't have "draw" command */
8249             cps->sendDrawOffers = 0;
8250             return;
8251         }
8252         if (cps->sendTime != 1 &&
8253             (StrStr(message, "time") || StrStr(message, "otim"))) {
8254           /* Program apparently doesn't have "time" or "otim" command */
8255           cps->sendTime = 0;
8256           return;
8257         }
8258         if (StrStr(message, "analyze")) {
8259             cps->analysisSupport = FALSE;
8260             cps->analyzing = FALSE;
8261 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8262             EditGameEvent(); // [HGM] try to preserve loaded game
8263             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8264             DisplayError(buf2, 0);
8265             return;
8266         }
8267         if (StrStr(message, "(no matching move)st")) {
8268           /* Special kludge for GNU Chess 4 only */
8269           cps->stKludge = TRUE;
8270           SendTimeControl(cps, movesPerSession, timeControl,
8271                           timeIncrement, appData.searchDepth,
8272                           searchTime);
8273           return;
8274         }
8275         if (StrStr(message, "(no matching move)sd")) {
8276           /* Special kludge for GNU Chess 4 only */
8277           cps->sdKludge = TRUE;
8278           SendTimeControl(cps, movesPerSession, timeControl,
8279                           timeIncrement, appData.searchDepth,
8280                           searchTime);
8281           return;
8282         }
8283         if (!StrStr(message, "llegal")) {
8284             return;
8285         }
8286         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8287             gameMode == IcsIdle) return;
8288         if (forwardMostMove <= backwardMostMove) return;
8289         if (pausing) PauseEvent();
8290       if(appData.forceIllegal) {
8291             // [HGM] illegal: machine refused move; force position after move into it
8292           SendToProgram("force\n", cps);
8293           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8294                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8295                 // when black is to move, while there might be nothing on a2 or black
8296                 // might already have the move. So send the board as if white has the move.
8297                 // But first we must change the stm of the engine, as it refused the last move
8298                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8299                 if(WhiteOnMove(forwardMostMove)) {
8300                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8301                     SendBoard(cps, forwardMostMove); // kludgeless board
8302                 } else {
8303                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8304                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8305                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8306                 }
8307           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8308             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8309                  gameMode == TwoMachinesPlay)
8310               SendToProgram("go\n", cps);
8311             return;
8312       } else
8313         if (gameMode == PlayFromGameFile) {
8314             /* Stop reading this game file */
8315             gameMode = EditGame;
8316             ModeHighlight();
8317         }
8318         /* [HGM] illegal-move claim should forfeit game when Xboard */
8319         /* only passes fully legal moves                            */
8320         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8321             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8322                                 "False illegal-move claim", GE_XBOARD );
8323             return; // do not take back move we tested as valid
8324         }
8325         currentMove = forwardMostMove-1;
8326         DisplayMove(currentMove-1); /* before DisplayMoveError */
8327         SwitchClocks(forwardMostMove-1); // [HGM] race
8328         DisplayBothClocks();
8329         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8330                 parseList[currentMove], _(cps->which));
8331         DisplayMoveError(buf1);
8332         DrawPosition(FALSE, boards[currentMove]);
8333
8334         SetUserThinkingEnables();
8335         return;
8336     }
8337     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8338         /* Program has a broken "time" command that
8339            outputs a string not ending in newline.
8340            Don't use it. */
8341         cps->sendTime = 0;
8342     }
8343
8344     /*
8345      * If chess program startup fails, exit with an error message.
8346      * Attempts to recover here are futile.
8347      */
8348     if ((StrStr(message, "unknown host") != NULL)
8349         || (StrStr(message, "No remote directory") != NULL)
8350         || (StrStr(message, "not found") != NULL)
8351         || (StrStr(message, "No such file") != NULL)
8352         || (StrStr(message, "can't alloc") != NULL)
8353         || (StrStr(message, "Permission denied") != NULL)) {
8354
8355         cps->maybeThinking = FALSE;
8356         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8357                 _(cps->which), cps->program, cps->host, message);
8358         RemoveInputSource(cps->isr);
8359         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8360             if(cps == &first) appData.noChessProgram = TRUE;
8361             DisplayError(buf1, 0);
8362         }
8363         return;
8364     }
8365
8366     /*
8367      * Look for hint output
8368      */
8369     if (sscanf(message, "Hint: %s", buf1) == 1) {
8370         if (cps == &first && hintRequested) {
8371             hintRequested = FALSE;
8372             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8373                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8374                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8375                                     PosFlags(forwardMostMove),
8376                                     fromY, fromX, toY, toX, promoChar, buf1);
8377                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8378                 DisplayInformation(buf2);
8379             } else {
8380                 /* Hint move could not be parsed!? */
8381               snprintf(buf2, sizeof(buf2),
8382                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8383                         buf1, _(cps->which));
8384                 DisplayError(buf2, 0);
8385             }
8386         } else {
8387           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8388         }
8389         return;
8390     }
8391
8392     /*
8393      * Ignore other messages if game is not in progress
8394      */
8395     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8396         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8397
8398     /*
8399      * look for win, lose, draw, or draw offer
8400      */
8401     if (strncmp(message, "1-0", 3) == 0) {
8402         char *p, *q, *r = "";
8403         p = strchr(message, '{');
8404         if (p) {
8405             q = strchr(p, '}');
8406             if (q) {
8407                 *q = NULLCHAR;
8408                 r = p + 1;
8409             }
8410         }
8411         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8412         return;
8413     } else if (strncmp(message, "0-1", 3) == 0) {
8414         char *p, *q, *r = "";
8415         p = strchr(message, '{');
8416         if (p) {
8417             q = strchr(p, '}');
8418             if (q) {
8419                 *q = NULLCHAR;
8420                 r = p + 1;
8421             }
8422         }
8423         /* Kludge for Arasan 4.1 bug */
8424         if (strcmp(r, "Black resigns") == 0) {
8425             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8426             return;
8427         }
8428         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8429         return;
8430     } else if (strncmp(message, "1/2", 3) == 0) {
8431         char *p, *q, *r = "";
8432         p = strchr(message, '{');
8433         if (p) {
8434             q = strchr(p, '}');
8435             if (q) {
8436                 *q = NULLCHAR;
8437                 r = p + 1;
8438             }
8439         }
8440
8441         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8442         return;
8443
8444     } else if (strncmp(message, "White resign", 12) == 0) {
8445         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8446         return;
8447     } else if (strncmp(message, "Black resign", 12) == 0) {
8448         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8449         return;
8450     } else if (strncmp(message, "White matches", 13) == 0 ||
8451                strncmp(message, "Black matches", 13) == 0   ) {
8452         /* [HGM] ignore GNUShogi noises */
8453         return;
8454     } else if (strncmp(message, "White", 5) == 0 &&
8455                message[5] != '(' &&
8456                StrStr(message, "Black") == NULL) {
8457         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8458         return;
8459     } else if (strncmp(message, "Black", 5) == 0 &&
8460                message[5] != '(') {
8461         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8462         return;
8463     } else if (strcmp(message, "resign") == 0 ||
8464                strcmp(message, "computer resigns") == 0) {
8465         switch (gameMode) {
8466           case MachinePlaysBlack:
8467           case IcsPlayingBlack:
8468             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8469             break;
8470           case MachinePlaysWhite:
8471           case IcsPlayingWhite:
8472             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8473             break;
8474           case TwoMachinesPlay:
8475             if (cps->twoMachinesColor[0] == 'w')
8476               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8477             else
8478               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8479             break;
8480           default:
8481             /* can't happen */
8482             break;
8483         }
8484         return;
8485     } else if (strncmp(message, "opponent mates", 14) == 0) {
8486         switch (gameMode) {
8487           case MachinePlaysBlack:
8488           case IcsPlayingBlack:
8489             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8490             break;
8491           case MachinePlaysWhite:
8492           case IcsPlayingWhite:
8493             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8494             break;
8495           case TwoMachinesPlay:
8496             if (cps->twoMachinesColor[0] == 'w')
8497               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8498             else
8499               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8500             break;
8501           default:
8502             /* can't happen */
8503             break;
8504         }
8505         return;
8506     } else if (strncmp(message, "computer mates", 14) == 0) {
8507         switch (gameMode) {
8508           case MachinePlaysBlack:
8509           case IcsPlayingBlack:
8510             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8511             break;
8512           case MachinePlaysWhite:
8513           case IcsPlayingWhite:
8514             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8515             break;
8516           case TwoMachinesPlay:
8517             if (cps->twoMachinesColor[0] == 'w')
8518               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8519             else
8520               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8521             break;
8522           default:
8523             /* can't happen */
8524             break;
8525         }
8526         return;
8527     } else if (strncmp(message, "checkmate", 9) == 0) {
8528         if (WhiteOnMove(forwardMostMove)) {
8529             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8530         } else {
8531             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8532         }
8533         return;
8534     } else if (strstr(message, "Draw") != NULL ||
8535                strstr(message, "game is a draw") != NULL) {
8536         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8537         return;
8538     } else if (strstr(message, "offer") != NULL &&
8539                strstr(message, "draw") != NULL) {
8540 #if ZIPPY
8541         if (appData.zippyPlay && first.initDone) {
8542             /* Relay offer to ICS */
8543             SendToICS(ics_prefix);
8544             SendToICS("draw\n");
8545         }
8546 #endif
8547         cps->offeredDraw = 2; /* valid until this engine moves twice */
8548         if (gameMode == TwoMachinesPlay) {
8549             if (cps->other->offeredDraw) {
8550                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8551             /* [HGM] in two-machine mode we delay relaying draw offer      */
8552             /* until after we also have move, to see if it is really claim */
8553             }
8554         } else if (gameMode == MachinePlaysWhite ||
8555                    gameMode == MachinePlaysBlack) {
8556           if (userOfferedDraw) {
8557             DisplayInformation(_("Machine accepts your draw offer"));
8558             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8559           } else {
8560             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8561           }
8562         }
8563     }
8564
8565
8566     /*
8567      * Look for thinking output
8568      */
8569     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8570           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8571                                 ) {
8572         int plylev, mvleft, mvtot, curscore, time;
8573         char mvname[MOVE_LEN];
8574         u64 nodes; // [DM]
8575         char plyext;
8576         int ignore = FALSE;
8577         int prefixHint = FALSE;
8578         mvname[0] = NULLCHAR;
8579
8580         switch (gameMode) {
8581           case MachinePlaysBlack:
8582           case IcsPlayingBlack:
8583             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8584             break;
8585           case MachinePlaysWhite:
8586           case IcsPlayingWhite:
8587             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8588             break;
8589           case AnalyzeMode:
8590           case AnalyzeFile:
8591             break;
8592           case IcsObserving: /* [DM] icsEngineAnalyze */
8593             if (!appData.icsEngineAnalyze) ignore = TRUE;
8594             break;
8595           case TwoMachinesPlay:
8596             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8597                 ignore = TRUE;
8598             }
8599             break;
8600           default:
8601             ignore = TRUE;
8602             break;
8603         }
8604
8605         if (!ignore) {
8606             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8607             buf1[0] = NULLCHAR;
8608             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8609                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8610
8611                 if (plyext != ' ' && plyext != '\t') {
8612                     time *= 100;
8613                 }
8614
8615                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8616                 if( cps->scoreIsAbsolute &&
8617                     ( gameMode == MachinePlaysBlack ||
8618                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8619                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8620                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8621                      !WhiteOnMove(currentMove)
8622                     ) )
8623                 {
8624                     curscore = -curscore;
8625                 }
8626
8627                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8628
8629                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8630                         char buf[MSG_SIZ];
8631                         FILE *f;
8632                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8633                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8634                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8635                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8636                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8637                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8638                                 fclose(f);
8639                         } else DisplayError(_("failed writing PV"), 0);
8640                 }
8641
8642                 tempStats.depth = plylev;
8643                 tempStats.nodes = nodes;
8644                 tempStats.time = time;
8645                 tempStats.score = curscore;
8646                 tempStats.got_only_move = 0;
8647
8648                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8649                         int ticklen;
8650
8651                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8652                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8653                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8654                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8655                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8656                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8657                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8658                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8659                 }
8660
8661                 /* Buffer overflow protection */
8662                 if (pv[0] != NULLCHAR) {
8663                     if (strlen(pv) >= sizeof(tempStats.movelist)
8664                         && appData.debugMode) {
8665                         fprintf(debugFP,
8666                                 "PV is too long; using the first %u bytes.\n",
8667                                 (unsigned) sizeof(tempStats.movelist) - 1);
8668                     }
8669
8670                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8671                 } else {
8672                     sprintf(tempStats.movelist, " no PV\n");
8673                 }
8674
8675                 if (tempStats.seen_stat) {
8676                     tempStats.ok_to_send = 1;
8677                 }
8678
8679                 if (strchr(tempStats.movelist, '(') != NULL) {
8680                     tempStats.line_is_book = 1;
8681                     tempStats.nr_moves = 0;
8682                     tempStats.moves_left = 0;
8683                 } else {
8684                     tempStats.line_is_book = 0;
8685                 }
8686
8687                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8688                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8689
8690                 SendProgramStatsToFrontend( cps, &tempStats );
8691
8692                 /*
8693                     [AS] Protect the thinkOutput buffer from overflow... this
8694                     is only useful if buf1 hasn't overflowed first!
8695                 */
8696                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8697                          plylev,
8698                          (gameMode == TwoMachinesPlay ?
8699                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8700                          ((double) curscore) / 100.0,
8701                          prefixHint ? lastHint : "",
8702                          prefixHint ? " " : "" );
8703
8704                 if( buf1[0] != NULLCHAR ) {
8705                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8706
8707                     if( strlen(pv) > max_len ) {
8708                         if( appData.debugMode) {
8709                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8710                         }
8711                         pv[max_len+1] = '\0';
8712                     }
8713
8714                     strcat( thinkOutput, pv);
8715                 }
8716
8717                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8718                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8719                     DisplayMove(currentMove - 1);
8720                 }
8721                 return;
8722
8723             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8724                 /* crafty (9.25+) says "(only move) <move>"
8725                  * if there is only 1 legal move
8726                  */
8727                 sscanf(p, "(only move) %s", buf1);
8728                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8729                 sprintf(programStats.movelist, "%s (only move)", buf1);
8730                 programStats.depth = 1;
8731                 programStats.nr_moves = 1;
8732                 programStats.moves_left = 1;
8733                 programStats.nodes = 1;
8734                 programStats.time = 1;
8735                 programStats.got_only_move = 1;
8736
8737                 /* Not really, but we also use this member to
8738                    mean "line isn't going to change" (Crafty
8739                    isn't searching, so stats won't change) */
8740                 programStats.line_is_book = 1;
8741
8742                 SendProgramStatsToFrontend( cps, &programStats );
8743
8744                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8745                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8746                     DisplayMove(currentMove - 1);
8747                 }
8748                 return;
8749             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8750                               &time, &nodes, &plylev, &mvleft,
8751                               &mvtot, mvname) >= 5) {
8752                 /* The stat01: line is from Crafty (9.29+) in response
8753                    to the "." command */
8754                 programStats.seen_stat = 1;
8755                 cps->maybeThinking = TRUE;
8756
8757                 if (programStats.got_only_move || !appData.periodicUpdates)
8758                   return;
8759
8760                 programStats.depth = plylev;
8761                 programStats.time = time;
8762                 programStats.nodes = nodes;
8763                 programStats.moves_left = mvleft;
8764                 programStats.nr_moves = mvtot;
8765                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8766                 programStats.ok_to_send = 1;
8767                 programStats.movelist[0] = '\0';
8768
8769                 SendProgramStatsToFrontend( cps, &programStats );
8770
8771                 return;
8772
8773             } else if (strncmp(message,"++",2) == 0) {
8774                 /* Crafty 9.29+ outputs this */
8775                 programStats.got_fail = 2;
8776                 return;
8777
8778             } else if (strncmp(message,"--",2) == 0) {
8779                 /* Crafty 9.29+ outputs this */
8780                 programStats.got_fail = 1;
8781                 return;
8782
8783             } else if (thinkOutput[0] != NULLCHAR &&
8784                        strncmp(message, "    ", 4) == 0) {
8785                 unsigned message_len;
8786
8787                 p = message;
8788                 while (*p && *p == ' ') p++;
8789
8790                 message_len = strlen( p );
8791
8792                 /* [AS] Avoid buffer overflow */
8793                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8794                     strcat(thinkOutput, " ");
8795                     strcat(thinkOutput, p);
8796                 }
8797
8798                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8799                     strcat(programStats.movelist, " ");
8800                     strcat(programStats.movelist, p);
8801                 }
8802
8803                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8804                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8805                     DisplayMove(currentMove - 1);
8806                 }
8807                 return;
8808             }
8809         }
8810         else {
8811             buf1[0] = NULLCHAR;
8812
8813             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8814                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8815             {
8816                 ChessProgramStats cpstats;
8817
8818                 if (plyext != ' ' && plyext != '\t') {
8819                     time *= 100;
8820                 }
8821
8822                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8823                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8824                     curscore = -curscore;
8825                 }
8826
8827                 cpstats.depth = plylev;
8828                 cpstats.nodes = nodes;
8829                 cpstats.time = time;
8830                 cpstats.score = curscore;
8831                 cpstats.got_only_move = 0;
8832                 cpstats.movelist[0] = '\0';
8833
8834                 if (buf1[0] != NULLCHAR) {
8835                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8836                 }
8837
8838                 cpstats.ok_to_send = 0;
8839                 cpstats.line_is_book = 0;
8840                 cpstats.nr_moves = 0;
8841                 cpstats.moves_left = 0;
8842
8843                 SendProgramStatsToFrontend( cps, &cpstats );
8844             }
8845         }
8846     }
8847 }
8848
8849
8850 /* Parse a game score from the character string "game", and
8851    record it as the history of the current game.  The game
8852    score is NOT assumed to start from the standard position.
8853    The display is not updated in any way.
8854    */
8855 void
8856 ParseGameHistory (char *game)
8857 {
8858     ChessMove moveType;
8859     int fromX, fromY, toX, toY, boardIndex;
8860     char promoChar;
8861     char *p, *q;
8862     char buf[MSG_SIZ];
8863
8864     if (appData.debugMode)
8865       fprintf(debugFP, "Parsing game history: %s\n", game);
8866
8867     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8868     gameInfo.site = StrSave(appData.icsHost);
8869     gameInfo.date = PGNDate();
8870     gameInfo.round = StrSave("-");
8871
8872     /* Parse out names of players */
8873     while (*game == ' ') game++;
8874     p = buf;
8875     while (*game != ' ') *p++ = *game++;
8876     *p = NULLCHAR;
8877     gameInfo.white = StrSave(buf);
8878     while (*game == ' ') game++;
8879     p = buf;
8880     while (*game != ' ' && *game != '\n') *p++ = *game++;
8881     *p = NULLCHAR;
8882     gameInfo.black = StrSave(buf);
8883
8884     /* Parse moves */
8885     boardIndex = blackPlaysFirst ? 1 : 0;
8886     yynewstr(game);
8887     for (;;) {
8888         yyboardindex = boardIndex;
8889         moveType = (ChessMove) Myylex();
8890         switch (moveType) {
8891           case IllegalMove:             /* maybe suicide chess, etc. */
8892   if (appData.debugMode) {
8893     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8894     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8895     setbuf(debugFP, NULL);
8896   }
8897           case WhitePromotion:
8898           case BlackPromotion:
8899           case WhiteNonPromotion:
8900           case BlackNonPromotion:
8901           case NormalMove:
8902           case WhiteCapturesEnPassant:
8903           case BlackCapturesEnPassant:
8904           case WhiteKingSideCastle:
8905           case WhiteQueenSideCastle:
8906           case BlackKingSideCastle:
8907           case BlackQueenSideCastle:
8908           case WhiteKingSideCastleWild:
8909           case WhiteQueenSideCastleWild:
8910           case BlackKingSideCastleWild:
8911           case BlackQueenSideCastleWild:
8912           /* PUSH Fabien */
8913           case WhiteHSideCastleFR:
8914           case WhiteASideCastleFR:
8915           case BlackHSideCastleFR:
8916           case BlackASideCastleFR:
8917           /* POP Fabien */
8918             fromX = currentMoveString[0] - AAA;
8919             fromY = currentMoveString[1] - ONE;
8920             toX = currentMoveString[2] - AAA;
8921             toY = currentMoveString[3] - ONE;
8922             promoChar = currentMoveString[4];
8923             break;
8924           case WhiteDrop:
8925           case BlackDrop:
8926             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8927             fromX = moveType == WhiteDrop ?
8928               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8929             (int) CharToPiece(ToLower(currentMoveString[0]));
8930             fromY = DROP_RANK;
8931             toX = currentMoveString[2] - AAA;
8932             toY = currentMoveString[3] - ONE;
8933             promoChar = NULLCHAR;
8934             break;
8935           case AmbiguousMove:
8936             /* bug? */
8937             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8938   if (appData.debugMode) {
8939     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8940     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8941     setbuf(debugFP, NULL);
8942   }
8943             DisplayError(buf, 0);
8944             return;
8945           case ImpossibleMove:
8946             /* bug? */
8947             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8948   if (appData.debugMode) {
8949     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8950     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8951     setbuf(debugFP, NULL);
8952   }
8953             DisplayError(buf, 0);
8954             return;
8955           case EndOfFile:
8956             if (boardIndex < backwardMostMove) {
8957                 /* Oops, gap.  How did that happen? */
8958                 DisplayError(_("Gap in move list"), 0);
8959                 return;
8960             }
8961             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8962             if (boardIndex > forwardMostMove) {
8963                 forwardMostMove = boardIndex;
8964             }
8965             return;
8966           case ElapsedTime:
8967             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8968                 strcat(parseList[boardIndex-1], " ");
8969                 strcat(parseList[boardIndex-1], yy_text);
8970             }
8971             continue;
8972           case Comment:
8973           case PGNTag:
8974           case NAG:
8975           default:
8976             /* ignore */
8977             continue;
8978           case WhiteWins:
8979           case BlackWins:
8980           case GameIsDrawn:
8981           case GameUnfinished:
8982             if (gameMode == IcsExamining) {
8983                 if (boardIndex < backwardMostMove) {
8984                     /* Oops, gap.  How did that happen? */
8985                     return;
8986                 }
8987                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8988                 return;
8989             }
8990             gameInfo.result = moveType;
8991             p = strchr(yy_text, '{');
8992             if (p == NULL) p = strchr(yy_text, '(');
8993             if (p == NULL) {
8994                 p = yy_text;
8995                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8996             } else {
8997                 q = strchr(p, *p == '{' ? '}' : ')');
8998                 if (q != NULL) *q = NULLCHAR;
8999                 p++;
9000             }
9001             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9002             gameInfo.resultDetails = StrSave(p);
9003             continue;
9004         }
9005         if (boardIndex >= forwardMostMove &&
9006             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9007             backwardMostMove = blackPlaysFirst ? 1 : 0;
9008             return;
9009         }
9010         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9011                                  fromY, fromX, toY, toX, promoChar,
9012                                  parseList[boardIndex]);
9013         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9014         /* currentMoveString is set as a side-effect of yylex */
9015         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9016         strcat(moveList[boardIndex], "\n");
9017         boardIndex++;
9018         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9019         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9020           case MT_NONE:
9021           case MT_STALEMATE:
9022           default:
9023             break;
9024           case MT_CHECK:
9025             if(gameInfo.variant != VariantShogi)
9026                 strcat(parseList[boardIndex - 1], "+");
9027             break;
9028           case MT_CHECKMATE:
9029           case MT_STAINMATE:
9030             strcat(parseList[boardIndex - 1], "#");
9031             break;
9032         }
9033     }
9034 }
9035
9036
9037 /* Apply a move to the given board  */
9038 void
9039 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9040 {
9041   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9042   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9043
9044     /* [HGM] compute & store e.p. status and castling rights for new position */
9045     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9046
9047       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9048       oldEP = (signed char)board[EP_STATUS];
9049       board[EP_STATUS] = EP_NONE;
9050
9051   if (fromY == DROP_RANK) {
9052         /* must be first */
9053         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9054             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9055             return;
9056         }
9057         piece = board[toY][toX] = (ChessSquare) fromX;
9058   } else {
9059       int i;
9060
9061       if( board[toY][toX] != EmptySquare )
9062            board[EP_STATUS] = EP_CAPTURE;
9063
9064       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9065            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9066                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9067       } else
9068       if( board[fromY][fromX] == WhitePawn ) {
9069            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9070                board[EP_STATUS] = EP_PAWN_MOVE;
9071            if( toY-fromY==2) {
9072                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9073                         gameInfo.variant != VariantBerolina || toX < fromX)
9074                       board[EP_STATUS] = toX | berolina;
9075                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9076                         gameInfo.variant != VariantBerolina || toX > fromX)
9077                       board[EP_STATUS] = toX;
9078            }
9079       } else
9080       if( board[fromY][fromX] == BlackPawn ) {
9081            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9082                board[EP_STATUS] = EP_PAWN_MOVE;
9083            if( toY-fromY== -2) {
9084                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9085                         gameInfo.variant != VariantBerolina || toX < fromX)
9086                       board[EP_STATUS] = toX | berolina;
9087                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9088                         gameInfo.variant != VariantBerolina || toX > fromX)
9089                       board[EP_STATUS] = toX;
9090            }
9091        }
9092
9093        for(i=0; i<nrCastlingRights; i++) {
9094            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9095               board[CASTLING][i] == toX   && castlingRank[i] == toY
9096              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9097        }
9098
9099      if (fromX == toX && fromY == toY) return;
9100
9101      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9102      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9103      if(gameInfo.variant == VariantKnightmate)
9104          king += (int) WhiteUnicorn - (int) WhiteKing;
9105
9106     /* Code added by Tord: */
9107     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9108     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9109         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9110       board[fromY][fromX] = EmptySquare;
9111       board[toY][toX] = EmptySquare;
9112       if((toX > fromX) != (piece == WhiteRook)) {
9113         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9114       } else {
9115         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9116       }
9117     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9118                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9119       board[fromY][fromX] = EmptySquare;
9120       board[toY][toX] = EmptySquare;
9121       if((toX > fromX) != (piece == BlackRook)) {
9122         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9123       } else {
9124         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9125       }
9126     /* End of code added by Tord */
9127
9128     } else if (board[fromY][fromX] == king
9129         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9130         && toY == fromY && toX > fromX+1) {
9131         board[fromY][fromX] = EmptySquare;
9132         board[toY][toX] = king;
9133         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9134         board[fromY][BOARD_RGHT-1] = EmptySquare;
9135     } else if (board[fromY][fromX] == king
9136         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9137                && toY == fromY && toX < fromX-1) {
9138         board[fromY][fromX] = EmptySquare;
9139         board[toY][toX] = king;
9140         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9141         board[fromY][BOARD_LEFT] = EmptySquare;
9142     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9143                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9144                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9145                ) {
9146         /* white pawn promotion */
9147         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9148         if(gameInfo.variant==VariantBughouse ||
9149            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9150             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9151         board[fromY][fromX] = EmptySquare;
9152     } else if ((fromY >= BOARD_HEIGHT>>1)
9153                && (toX != fromX)
9154                && gameInfo.variant != VariantXiangqi
9155                && gameInfo.variant != VariantBerolina
9156                && (board[fromY][fromX] == WhitePawn)
9157                && (board[toY][toX] == EmptySquare)) {
9158         board[fromY][fromX] = EmptySquare;
9159         board[toY][toX] = WhitePawn;
9160         captured = board[toY - 1][toX];
9161         board[toY - 1][toX] = EmptySquare;
9162     } else if ((fromY == BOARD_HEIGHT-4)
9163                && (toX == fromX)
9164                && gameInfo.variant == VariantBerolina
9165                && (board[fromY][fromX] == WhitePawn)
9166                && (board[toY][toX] == EmptySquare)) {
9167         board[fromY][fromX] = EmptySquare;
9168         board[toY][toX] = WhitePawn;
9169         if(oldEP & EP_BEROLIN_A) {
9170                 captured = board[fromY][fromX-1];
9171                 board[fromY][fromX-1] = EmptySquare;
9172         }else{  captured = board[fromY][fromX+1];
9173                 board[fromY][fromX+1] = EmptySquare;
9174         }
9175     } else if (board[fromY][fromX] == king
9176         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9177                && toY == fromY && toX > fromX+1) {
9178         board[fromY][fromX] = EmptySquare;
9179         board[toY][toX] = king;
9180         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9181         board[fromY][BOARD_RGHT-1] = EmptySquare;
9182     } else if (board[fromY][fromX] == king
9183         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9184                && toY == fromY && toX < fromX-1) {
9185         board[fromY][fromX] = EmptySquare;
9186         board[toY][toX] = king;
9187         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9188         board[fromY][BOARD_LEFT] = EmptySquare;
9189     } else if (fromY == 7 && fromX == 3
9190                && board[fromY][fromX] == BlackKing
9191                && toY == 7 && toX == 5) {
9192         board[fromY][fromX] = EmptySquare;
9193         board[toY][toX] = BlackKing;
9194         board[fromY][7] = EmptySquare;
9195         board[toY][4] = BlackRook;
9196     } else if (fromY == 7 && fromX == 3
9197                && board[fromY][fromX] == BlackKing
9198                && toY == 7 && toX == 1) {
9199         board[fromY][fromX] = EmptySquare;
9200         board[toY][toX] = BlackKing;
9201         board[fromY][0] = EmptySquare;
9202         board[toY][2] = BlackRook;
9203     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9204                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9205                && toY < promoRank && promoChar
9206                ) {
9207         /* black pawn promotion */
9208         board[toY][toX] = CharToPiece(ToLower(promoChar));
9209         if(gameInfo.variant==VariantBughouse ||
9210            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9211             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9212         board[fromY][fromX] = EmptySquare;
9213     } else if ((fromY < BOARD_HEIGHT>>1)
9214                && (toX != fromX)
9215                && gameInfo.variant != VariantXiangqi
9216                && gameInfo.variant != VariantBerolina
9217                && (board[fromY][fromX] == BlackPawn)
9218                && (board[toY][toX] == EmptySquare)) {
9219         board[fromY][fromX] = EmptySquare;
9220         board[toY][toX] = BlackPawn;
9221         captured = board[toY + 1][toX];
9222         board[toY + 1][toX] = EmptySquare;
9223     } else if ((fromY == 3)
9224                && (toX == fromX)
9225                && gameInfo.variant == VariantBerolina
9226                && (board[fromY][fromX] == BlackPawn)
9227                && (board[toY][toX] == EmptySquare)) {
9228         board[fromY][fromX] = EmptySquare;
9229         board[toY][toX] = BlackPawn;
9230         if(oldEP & EP_BEROLIN_A) {
9231                 captured = board[fromY][fromX-1];
9232                 board[fromY][fromX-1] = EmptySquare;
9233         }else{  captured = board[fromY][fromX+1];
9234                 board[fromY][fromX+1] = EmptySquare;
9235         }
9236     } else {
9237         board[toY][toX] = board[fromY][fromX];
9238         board[fromY][fromX] = EmptySquare;
9239     }
9240   }
9241
9242     if (gameInfo.holdingsWidth != 0) {
9243
9244       /* !!A lot more code needs to be written to support holdings  */
9245       /* [HGM] OK, so I have written it. Holdings are stored in the */
9246       /* penultimate board files, so they are automaticlly stored   */
9247       /* in the game history.                                       */
9248       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9249                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9250         /* Delete from holdings, by decreasing count */
9251         /* and erasing image if necessary            */
9252         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9253         if(p < (int) BlackPawn) { /* white drop */
9254              p -= (int)WhitePawn;
9255                  p = PieceToNumber((ChessSquare)p);
9256              if(p >= gameInfo.holdingsSize) p = 0;
9257              if(--board[p][BOARD_WIDTH-2] <= 0)
9258                   board[p][BOARD_WIDTH-1] = EmptySquare;
9259              if((int)board[p][BOARD_WIDTH-2] < 0)
9260                         board[p][BOARD_WIDTH-2] = 0;
9261         } else {                  /* black drop */
9262              p -= (int)BlackPawn;
9263                  p = PieceToNumber((ChessSquare)p);
9264              if(p >= gameInfo.holdingsSize) p = 0;
9265              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9266                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9267              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9268                         board[BOARD_HEIGHT-1-p][1] = 0;
9269         }
9270       }
9271       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9272           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9273         /* [HGM] holdings: Add to holdings, if holdings exist */
9274         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9275                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9276                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9277         }
9278         p = (int) captured;
9279         if (p >= (int) BlackPawn) {
9280           p -= (int)BlackPawn;
9281           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9282                   /* in Shogi restore piece to its original  first */
9283                   captured = (ChessSquare) (DEMOTED captured);
9284                   p = DEMOTED p;
9285           }
9286           p = PieceToNumber((ChessSquare)p);
9287           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9288           board[p][BOARD_WIDTH-2]++;
9289           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9290         } else {
9291           p -= (int)WhitePawn;
9292           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9293                   captured = (ChessSquare) (DEMOTED captured);
9294                   p = DEMOTED p;
9295           }
9296           p = PieceToNumber((ChessSquare)p);
9297           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9298           board[BOARD_HEIGHT-1-p][1]++;
9299           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9300         }
9301       }
9302     } else if (gameInfo.variant == VariantAtomic) {
9303       if (captured != EmptySquare) {
9304         int y, x;
9305         for (y = toY-1; y <= toY+1; y++) {
9306           for (x = toX-1; x <= toX+1; x++) {
9307             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9308                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9309               board[y][x] = EmptySquare;
9310             }
9311           }
9312         }
9313         board[toY][toX] = EmptySquare;
9314       }
9315     }
9316     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9317         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9318     } else
9319     if(promoChar == '+') {
9320         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9321         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9322     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9323         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9324     }
9325     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9326                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9327         // [HGM] superchess: take promotion piece out of holdings
9328         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9329         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9330             if(!--board[k][BOARD_WIDTH-2])
9331                 board[k][BOARD_WIDTH-1] = EmptySquare;
9332         } else {
9333             if(!--board[BOARD_HEIGHT-1-k][1])
9334                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9335         }
9336     }
9337
9338 }
9339
9340 /* Updates forwardMostMove */
9341 void
9342 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9343 {
9344 //    forwardMostMove++; // [HGM] bare: moved downstream
9345
9346     (void) CoordsToAlgebraic(boards[forwardMostMove],
9347                              PosFlags(forwardMostMove),
9348                              fromY, fromX, toY, toX, promoChar,
9349                              parseList[forwardMostMove]);
9350
9351     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9352         int timeLeft; static int lastLoadFlag=0; int king, piece;
9353         piece = boards[forwardMostMove][fromY][fromX];
9354         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9355         if(gameInfo.variant == VariantKnightmate)
9356             king += (int) WhiteUnicorn - (int) WhiteKing;
9357         if(forwardMostMove == 0) {
9358             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9359                 fprintf(serverMoves, "%s;", UserName());
9360             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9361                 fprintf(serverMoves, "%s;", second.tidy);
9362             fprintf(serverMoves, "%s;", first.tidy);
9363             if(gameMode == MachinePlaysWhite)
9364                 fprintf(serverMoves, "%s;", UserName());
9365             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9366                 fprintf(serverMoves, "%s;", second.tidy);
9367         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9368         lastLoadFlag = loadFlag;
9369         // print base move
9370         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9371         // print castling suffix
9372         if( toY == fromY && piece == king ) {
9373             if(toX-fromX > 1)
9374                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9375             if(fromX-toX >1)
9376                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9377         }
9378         // e.p. suffix
9379         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9380              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9381              boards[forwardMostMove][toY][toX] == EmptySquare
9382              && fromX != toX && fromY != toY)
9383                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9384         // promotion suffix
9385         if(promoChar != NULLCHAR)
9386                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9387         if(!loadFlag) {
9388                 char buf[MOVE_LEN*2], *p; int len;
9389             fprintf(serverMoves, "/%d/%d",
9390                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9391             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9392             else                      timeLeft = blackTimeRemaining/1000;
9393             fprintf(serverMoves, "/%d", timeLeft);
9394                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9395                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9396                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9397             fprintf(serverMoves, "/%s", buf);
9398         }
9399         fflush(serverMoves);
9400     }
9401
9402     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9403         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9404       return;
9405     }
9406     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9407     if (commentList[forwardMostMove+1] != NULL) {
9408         free(commentList[forwardMostMove+1]);
9409         commentList[forwardMostMove+1] = NULL;
9410     }
9411     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9412     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9413     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9414     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9415     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9416     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9417     adjustedClock = FALSE;
9418     gameInfo.result = GameUnfinished;
9419     if (gameInfo.resultDetails != NULL) {
9420         free(gameInfo.resultDetails);
9421         gameInfo.resultDetails = NULL;
9422     }
9423     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9424                               moveList[forwardMostMove - 1]);
9425     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9426       case MT_NONE:
9427       case MT_STALEMATE:
9428       default:
9429         break;
9430       case MT_CHECK:
9431         if(gameInfo.variant != VariantShogi)
9432             strcat(parseList[forwardMostMove - 1], "+");
9433         break;
9434       case MT_CHECKMATE:
9435       case MT_STAINMATE:
9436         strcat(parseList[forwardMostMove - 1], "#");
9437         break;
9438     }
9439     if (appData.debugMode) {
9440         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9441     }
9442
9443 }
9444
9445 /* Updates currentMove if not pausing */
9446 void
9447 ShowMove (int fromX, int fromY, int toX, int toY)
9448 {
9449     int instant = (gameMode == PlayFromGameFile) ?
9450         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9451     if(appData.noGUI) return;
9452     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9453         if (!instant) {
9454             if (forwardMostMove == currentMove + 1) {
9455                 AnimateMove(boards[forwardMostMove - 1],
9456                             fromX, fromY, toX, toY);
9457             }
9458             if (appData.highlightLastMove) {
9459                 SetHighlights(fromX, fromY, toX, toY);
9460             }
9461         }
9462         currentMove = forwardMostMove;
9463     }
9464
9465     if (instant) return;
9466
9467     DisplayMove(currentMove - 1);
9468     DrawPosition(FALSE, boards[currentMove]);
9469     DisplayBothClocks();
9470     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9471 }
9472
9473 void
9474 SendEgtPath (ChessProgramState *cps)
9475 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9476         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9477
9478         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9479
9480         while(*p) {
9481             char c, *q = name+1, *r, *s;
9482
9483             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9484             while(*p && *p != ',') *q++ = *p++;
9485             *q++ = ':'; *q = 0;
9486             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9487                 strcmp(name, ",nalimov:") == 0 ) {
9488                 // take nalimov path from the menu-changeable option first, if it is defined
9489               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9490                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9491             } else
9492             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9493                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9494                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9495                 s = r = StrStr(s, ":") + 1; // beginning of path info
9496                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9497                 c = *r; *r = 0;             // temporarily null-terminate path info
9498                     *--q = 0;               // strip of trailig ':' from name
9499                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9500                 *r = c;
9501                 SendToProgram(buf,cps);     // send egtbpath command for this format
9502             }
9503             if(*p == ',') p++; // read away comma to position for next format name
9504         }
9505 }
9506
9507 void
9508 InitChessProgram (ChessProgramState *cps, int setup)
9509 /* setup needed to setup FRC opening position */
9510 {
9511     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9512     if (appData.noChessProgram) return;
9513     hintRequested = FALSE;
9514     bookRequested = FALSE;
9515
9516     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9517     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9518     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9519     if(cps->memSize) { /* [HGM] memory */
9520       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9521         SendToProgram(buf, cps);
9522     }
9523     SendEgtPath(cps); /* [HGM] EGT */
9524     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9525       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9526         SendToProgram(buf, cps);
9527     }
9528
9529     SendToProgram(cps->initString, cps);
9530     if (gameInfo.variant != VariantNormal &&
9531         gameInfo.variant != VariantLoadable
9532         /* [HGM] also send variant if board size non-standard */
9533         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9534                                             ) {
9535       char *v = VariantName(gameInfo.variant);
9536       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9537         /* [HGM] in protocol 1 we have to assume all variants valid */
9538         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9539         DisplayFatalError(buf, 0, 1);
9540         return;
9541       }
9542
9543       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9544       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9545       if( gameInfo.variant == VariantXiangqi )
9546            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9547       if( gameInfo.variant == VariantShogi )
9548            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9549       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9550            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9551       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9552           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9553            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9554       if( gameInfo.variant == VariantCourier )
9555            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9556       if( gameInfo.variant == VariantSuper )
9557            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9558       if( gameInfo.variant == VariantGreat )
9559            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9560       if( gameInfo.variant == VariantSChess )
9561            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9562       if( gameInfo.variant == VariantGrand )
9563            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9564
9565       if(overruled) {
9566         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9567                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9568            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9569            if(StrStr(cps->variants, b) == NULL) {
9570                // specific sized variant not known, check if general sizing allowed
9571                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9572                    if(StrStr(cps->variants, "boardsize") == NULL) {
9573                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9574                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9575                        DisplayFatalError(buf, 0, 1);
9576                        return;
9577                    }
9578                    /* [HGM] here we really should compare with the maximum supported board size */
9579                }
9580            }
9581       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9582       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9583       SendToProgram(buf, cps);
9584     }
9585     currentlyInitializedVariant = gameInfo.variant;
9586
9587     /* [HGM] send opening position in FRC to first engine */
9588     if(setup) {
9589           SendToProgram("force\n", cps);
9590           SendBoard(cps, 0);
9591           /* engine is now in force mode! Set flag to wake it up after first move. */
9592           setboardSpoiledMachineBlack = 1;
9593     }
9594
9595     if (cps->sendICS) {
9596       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9597       SendToProgram(buf, cps);
9598     }
9599     cps->maybeThinking = FALSE;
9600     cps->offeredDraw = 0;
9601     if (!appData.icsActive) {
9602         SendTimeControl(cps, movesPerSession, timeControl,
9603                         timeIncrement, appData.searchDepth,
9604                         searchTime);
9605     }
9606     if (appData.showThinking
9607         // [HGM] thinking: four options require thinking output to be sent
9608         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9609                                 ) {
9610         SendToProgram("post\n", cps);
9611     }
9612     SendToProgram("hard\n", cps);
9613     if (!appData.ponderNextMove) {
9614         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9615            it without being sure what state we are in first.  "hard"
9616            is not a toggle, so that one is OK.
9617          */
9618         SendToProgram("easy\n", cps);
9619     }
9620     if (cps->usePing) {
9621       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9622       SendToProgram(buf, cps);
9623     }
9624     cps->initDone = TRUE;
9625     ClearEngineOutputPane(cps == &second);
9626 }
9627
9628
9629 void
9630 StartChessProgram (ChessProgramState *cps)
9631 {
9632     char buf[MSG_SIZ];
9633     int err;
9634
9635     if (appData.noChessProgram) return;
9636     cps->initDone = FALSE;
9637
9638     if (strcmp(cps->host, "localhost") == 0) {
9639         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9640     } else if (*appData.remoteShell == NULLCHAR) {
9641         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9642     } else {
9643         if (*appData.remoteUser == NULLCHAR) {
9644           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9645                     cps->program);
9646         } else {
9647           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9648                     cps->host, appData.remoteUser, cps->program);
9649         }
9650         err = StartChildProcess(buf, "", &cps->pr);
9651     }
9652
9653     if (err != 0) {
9654       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9655         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9656         if(cps != &first) return;
9657         appData.noChessProgram = TRUE;
9658         ThawUI();
9659         SetNCPMode();
9660 //      DisplayFatalError(buf, err, 1);
9661 //      cps->pr = NoProc;
9662 //      cps->isr = NULL;
9663         return;
9664     }
9665
9666     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9667     if (cps->protocolVersion > 1) {
9668       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9669       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9670       cps->comboCnt = 0;  //                and values of combo boxes
9671       SendToProgram(buf, cps);
9672     } else {
9673       SendToProgram("xboard\n", cps);
9674     }
9675 }
9676
9677 void
9678 TwoMachinesEventIfReady P((void))
9679 {
9680   static int curMess = 0;
9681   if (first.lastPing != first.lastPong) {
9682     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9683     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9684     return;
9685   }
9686   if (second.lastPing != second.lastPong) {
9687     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9688     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9689     return;
9690   }
9691   DisplayMessage("", ""); curMess = 0;
9692   ThawUI();
9693   TwoMachinesEvent();
9694 }
9695
9696 char *
9697 MakeName (char *template)
9698 {
9699     time_t clock;
9700     struct tm *tm;
9701     static char buf[MSG_SIZ];
9702     char *p = buf;
9703     int i;
9704
9705     clock = time((time_t *)NULL);
9706     tm = localtime(&clock);
9707
9708     while(*p++ = *template++) if(p[-1] == '%') {
9709         switch(*template++) {
9710           case 0:   *p = 0; return buf;
9711           case 'Y': i = tm->tm_year+1900; break;
9712           case 'y': i = tm->tm_year-100; break;
9713           case 'M': i = tm->tm_mon+1; break;
9714           case 'd': i = tm->tm_mday; break;
9715           case 'h': i = tm->tm_hour; break;
9716           case 'm': i = tm->tm_min; break;
9717           case 's': i = tm->tm_sec; break;
9718           default:  i = 0;
9719         }
9720         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9721     }
9722     return buf;
9723 }
9724
9725 int
9726 CountPlayers (char *p)
9727 {
9728     int n = 0;
9729     while(p = strchr(p, '\n')) p++, n++; // count participants
9730     return n;
9731 }
9732
9733 FILE *
9734 WriteTourneyFile (char *results, FILE *f)
9735 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9736     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9737     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9738         // create a file with tournament description
9739         fprintf(f, "-participants {%s}\n", appData.participants);
9740         fprintf(f, "-seedBase %d\n", appData.seedBase);
9741         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9742         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9743         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9744         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9745         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9746         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9747         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9748         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9749         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9750         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9751         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9752         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9753         if(searchTime > 0)
9754                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9755         else {
9756                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9757                 fprintf(f, "-tc %s\n", appData.timeControl);
9758                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9759         }
9760         fprintf(f, "-results \"%s\"\n", results);
9761     }
9762     return f;
9763 }
9764
9765 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9766
9767 void
9768 Substitute (char *participants, int expunge)
9769 {
9770     int i, changed, changes=0, nPlayers=0;
9771     char *p, *q, *r, buf[MSG_SIZ];
9772     if(participants == NULL) return;
9773     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9774     r = p = participants; q = appData.participants;
9775     while(*p && *p == *q) {
9776         if(*p == '\n') r = p+1, nPlayers++;
9777         p++; q++;
9778     }
9779     if(*p) { // difference
9780         while(*p && *p++ != '\n');
9781         while(*q && *q++ != '\n');
9782       changed = nPlayers;
9783         changes = 1 + (strcmp(p, q) != 0);
9784     }
9785     if(changes == 1) { // a single engine mnemonic was changed
9786         q = r; while(*q) nPlayers += (*q++ == '\n');
9787         p = buf; while(*r && (*p = *r++) != '\n') p++;
9788         *p = NULLCHAR;
9789         NamesToList(firstChessProgramNames, command, mnemonic);
9790         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9791         if(mnemonic[i]) { // The substitute is valid
9792             FILE *f;
9793             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9794                 flock(fileno(f), LOCK_EX);
9795                 ParseArgsFromFile(f);
9796                 fseek(f, 0, SEEK_SET);
9797                 FREE(appData.participants); appData.participants = participants;
9798                 if(expunge) { // erase results of replaced engine
9799                     int len = strlen(appData.results), w, b, dummy;
9800                     for(i=0; i<len; i++) {
9801                         Pairing(i, nPlayers, &w, &b, &dummy);
9802                         if((w == changed || b == changed) && appData.results[i] == '*') {
9803                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9804                             fclose(f);
9805                             return;
9806                         }
9807                     }
9808                     for(i=0; i<len; i++) {
9809                         Pairing(i, nPlayers, &w, &b, &dummy);
9810                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9811                     }
9812                 }
9813                 WriteTourneyFile(appData.results, f);
9814                 fclose(f); // release lock
9815                 return;
9816             }
9817         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9818     }
9819     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9820     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9821     free(participants);
9822     return;
9823 }
9824
9825 int
9826 CreateTourney (char *name)
9827 {
9828         FILE *f;
9829         if(matchMode && strcmp(name, appData.tourneyFile)) {
9830              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9831         }
9832         if(name[0] == NULLCHAR) {
9833             if(appData.participants[0])
9834                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9835             return 0;
9836         }
9837         f = fopen(name, "r");
9838         if(f) { // file exists
9839             ASSIGN(appData.tourneyFile, name);
9840             ParseArgsFromFile(f); // parse it
9841         } else {
9842             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9843             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9844                 DisplayError(_("Not enough participants"), 0);
9845                 return 0;
9846             }
9847             ASSIGN(appData.tourneyFile, name);
9848             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9849             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9850         }
9851         fclose(f);
9852         appData.noChessProgram = FALSE;
9853         appData.clockMode = TRUE;
9854         SetGNUMode();
9855         return 1;
9856 }
9857
9858 void
9859 NamesToList (char *names, char **engineList, char **engineMnemonic)
9860 {
9861     char buf[MSG_SIZ], *p, *q;
9862     int i=1;
9863     while(*names) {
9864         p = names; q = buf;
9865         while(*p && *p != '\n') *q++ = *p++;
9866         *q = 0;
9867         if(engineList[i]) free(engineList[i]);
9868         engineList[i] = strdup(buf);
9869         if(*p == '\n') p++;
9870         TidyProgramName(engineList[i], "localhost", buf);
9871         if(engineMnemonic[i]) free(engineMnemonic[i]);
9872         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9873             strcat(buf, " (");
9874             sscanf(q + 8, "%s", buf + strlen(buf));
9875             strcat(buf, ")");
9876         }
9877         engineMnemonic[i] = strdup(buf);
9878         names = p; i++;
9879       if(i > MAXENGINES - 2) break;
9880     }
9881     engineList[i] = engineMnemonic[i] = NULL;
9882 }
9883
9884 // following implemented as macro to avoid type limitations
9885 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9886
9887 void
9888 SwapEngines (int n)
9889 {   // swap settings for first engine and other engine (so far only some selected options)
9890     int h;
9891     char *p;
9892     if(n == 0) return;
9893     SWAP(directory, p)
9894     SWAP(chessProgram, p)
9895     SWAP(isUCI, h)
9896     SWAP(hasOwnBookUCI, h)
9897     SWAP(protocolVersion, h)
9898     SWAP(reuse, h)
9899     SWAP(scoreIsAbsolute, h)
9900     SWAP(timeOdds, h)
9901     SWAP(logo, p)
9902     SWAP(pgnName, p)
9903     SWAP(pvSAN, h)
9904     SWAP(engOptions, p)
9905 }
9906
9907 void
9908 SetPlayer (int player)
9909 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9910     int i;
9911     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9912     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9913     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9914     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9915     if(mnemonic[i]) {
9916         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9917         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9918         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
9919         ParseArgsFromString(buf);
9920     }
9921     free(engineName);
9922 }
9923
9924 int
9925 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9926 {   // determine players from game number
9927     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9928
9929     if(appData.tourneyType == 0) {
9930         roundsPerCycle = (nPlayers - 1) | 1;
9931         pairingsPerRound = nPlayers / 2;
9932     } else if(appData.tourneyType > 0) {
9933         roundsPerCycle = nPlayers - appData.tourneyType;
9934         pairingsPerRound = appData.tourneyType;
9935     }
9936     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9937     gamesPerCycle = gamesPerRound * roundsPerCycle;
9938     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9939     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9940     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9941     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9942     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9943     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9944
9945     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9946     if(appData.roundSync) *syncInterval = gamesPerRound;
9947
9948     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9949
9950     if(appData.tourneyType == 0) {
9951         if(curPairing == (nPlayers-1)/2 ) {
9952             *whitePlayer = curRound;
9953             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9954         } else {
9955             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9956             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9957             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9958             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9959         }
9960     } else if(appData.tourneyType > 1) {
9961         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
9962         *whitePlayer = curRound + appData.tourneyType;
9963     } else if(appData.tourneyType > 0) {
9964         *whitePlayer = curPairing;
9965         *blackPlayer = curRound + appData.tourneyType;
9966     }
9967
9968     // take care of white/black alternation per round. 
9969     // For cycles and games this is already taken care of by default, derived from matchGame!
9970     return curRound & 1;
9971 }
9972
9973 int
9974 NextTourneyGame (int nr, int *swapColors)
9975 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9976     char *p, *q;
9977     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9978     FILE *tf;
9979     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9980     tf = fopen(appData.tourneyFile, "r");
9981     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9982     ParseArgsFromFile(tf); fclose(tf);
9983     InitTimeControls(); // TC might be altered from tourney file
9984
9985     nPlayers = CountPlayers(appData.participants); // count participants
9986     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
9987     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9988
9989     if(syncInterval) {
9990         p = q = appData.results;
9991         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9992         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9993             DisplayMessage(_("Waiting for other game(s)"),"");
9994             waitingForGame = TRUE;
9995             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9996             return 0;
9997         }
9998         waitingForGame = FALSE;
9999     }
10000
10001     if(appData.tourneyType < 0) {
10002         if(nr>=0 && !pairingReceived) {
10003             char buf[1<<16];
10004             if(pairing.pr == NoProc) {
10005                 if(!appData.pairingEngine[0]) {
10006                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10007                     return 0;
10008                 }
10009                 StartChessProgram(&pairing); // starts the pairing engine
10010             }
10011             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10012             SendToProgram(buf, &pairing);
10013             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10014             SendToProgram(buf, &pairing);
10015             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10016         }
10017         pairingReceived = 0;                              // ... so we continue here 
10018         *swapColors = 0;
10019         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10020         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10021         matchGame = 1; roundNr = nr / syncInterval + 1;
10022     }
10023
10024     if(first.pr != NoProc && second.pr != NoProc) return 1; // engines already loaded
10025
10026     // redefine engines, engine dir, etc.
10027     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10028     if(first.pr == NoProc || nr < 0) {
10029       SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
10030       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10031     }
10032     if(second.pr == NoProc) {
10033       SwapEngines(1);
10034       SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
10035       SwapEngines(1);         // and make that valid for second engine by swapping
10036       InitEngine(&second, 1);
10037     }
10038     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10039     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10040     return 1;
10041 }
10042
10043 void
10044 NextMatchGame ()
10045 {   // performs game initialization that does not invoke engines, and then tries to start the game
10046     int res, firstWhite, swapColors = 0;
10047     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10048     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10049     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10050     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10051     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10052     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10053     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10054     Reset(FALSE, first.pr != NoProc);
10055     res = LoadGameOrPosition(matchGame); // setup game
10056     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10057     if(!res) return; // abort when bad game/pos file
10058     TwoMachinesEvent();
10059 }
10060
10061 void
10062 UserAdjudicationEvent (int result)
10063 {
10064     ChessMove gameResult = GameIsDrawn;
10065
10066     if( result > 0 ) {
10067         gameResult = WhiteWins;
10068     }
10069     else if( result < 0 ) {
10070         gameResult = BlackWins;
10071     }
10072
10073     if( gameMode == TwoMachinesPlay ) {
10074         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10075     }
10076 }
10077
10078
10079 // [HGM] save: calculate checksum of game to make games easily identifiable
10080 int
10081 StringCheckSum (char *s)
10082 {
10083         int i = 0;
10084         if(s==NULL) return 0;
10085         while(*s) i = i*259 + *s++;
10086         return i;
10087 }
10088
10089 int
10090 GameCheckSum ()
10091 {
10092         int i, sum=0;
10093         for(i=backwardMostMove; i<forwardMostMove; i++) {
10094                 sum += pvInfoList[i].depth;
10095                 sum += StringCheckSum(parseList[i]);
10096                 sum += StringCheckSum(commentList[i]);
10097                 sum *= 261;
10098         }
10099         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10100         return sum + StringCheckSum(commentList[i]);
10101 } // end of save patch
10102
10103 void
10104 GameEnds (ChessMove result, char *resultDetails, int whosays)
10105 {
10106     GameMode nextGameMode;
10107     int isIcsGame;
10108     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10109
10110     if(endingGame) return; /* [HGM] crash: forbid recursion */
10111     endingGame = 1;
10112     if(twoBoards) { // [HGM] dual: switch back to one board
10113         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10114         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10115     }
10116     if (appData.debugMode) {
10117       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10118               result, resultDetails ? resultDetails : "(null)", whosays);
10119     }
10120
10121     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10122
10123     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10124         /* If we are playing on ICS, the server decides when the
10125            game is over, but the engine can offer to draw, claim
10126            a draw, or resign.
10127          */
10128 #if ZIPPY
10129         if (appData.zippyPlay && first.initDone) {
10130             if (result == GameIsDrawn) {
10131                 /* In case draw still needs to be claimed */
10132                 SendToICS(ics_prefix);
10133                 SendToICS("draw\n");
10134             } else if (StrCaseStr(resultDetails, "resign")) {
10135                 SendToICS(ics_prefix);
10136                 SendToICS("resign\n");
10137             }
10138         }
10139 #endif
10140         endingGame = 0; /* [HGM] crash */
10141         return;
10142     }
10143
10144     /* If we're loading the game from a file, stop */
10145     if (whosays == GE_FILE) {
10146       (void) StopLoadGameTimer();
10147       gameFileFP = NULL;
10148     }
10149
10150     /* Cancel draw offers */
10151     first.offeredDraw = second.offeredDraw = 0;
10152
10153     /* If this is an ICS game, only ICS can really say it's done;
10154        if not, anyone can. */
10155     isIcsGame = (gameMode == IcsPlayingWhite ||
10156                  gameMode == IcsPlayingBlack ||
10157                  gameMode == IcsObserving    ||
10158                  gameMode == IcsExamining);
10159
10160     if (!isIcsGame || whosays == GE_ICS) {
10161         /* OK -- not an ICS game, or ICS said it was done */
10162         StopClocks();
10163         if (!isIcsGame && !appData.noChessProgram)
10164           SetUserThinkingEnables();
10165
10166         /* [HGM] if a machine claims the game end we verify this claim */
10167         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10168             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10169                 char claimer;
10170                 ChessMove trueResult = (ChessMove) -1;
10171
10172                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10173                                             first.twoMachinesColor[0] :
10174                                             second.twoMachinesColor[0] ;
10175
10176                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10177                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10178                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10179                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10180                 } else
10181                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10182                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10183                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10184                 } else
10185                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10186                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10187                 }
10188
10189                 // now verify win claims, but not in drop games, as we don't understand those yet
10190                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10191                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10192                     (result == WhiteWins && claimer == 'w' ||
10193                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10194                       if (appData.debugMode) {
10195                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10196                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10197                       }
10198                       if(result != trueResult) {
10199                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10200                               result = claimer == 'w' ? BlackWins : WhiteWins;
10201                               resultDetails = buf;
10202                       }
10203                 } else
10204                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10205                     && (forwardMostMove <= backwardMostMove ||
10206                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10207                         (claimer=='b')==(forwardMostMove&1))
10208                                                                                   ) {
10209                       /* [HGM] verify: draws that were not flagged are false claims */
10210                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10211                       result = claimer == 'w' ? BlackWins : WhiteWins;
10212                       resultDetails = buf;
10213                 }
10214                 /* (Claiming a loss is accepted no questions asked!) */
10215             }
10216             /* [HGM] bare: don't allow bare King to win */
10217             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10218                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10219                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10220                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10221                && result != GameIsDrawn)
10222             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10223                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10224                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10225                         if(p >= 0 && p <= (int)WhiteKing) k++;
10226                 }
10227                 if (appData.debugMode) {
10228                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10229                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10230                 }
10231                 if(k <= 1) {
10232                         result = GameIsDrawn;
10233                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10234                         resultDetails = buf;
10235                 }
10236             }
10237         }
10238
10239
10240         if(serverMoves != NULL && !loadFlag) { char c = '=';
10241             if(result==WhiteWins) c = '+';
10242             if(result==BlackWins) c = '-';
10243             if(resultDetails != NULL)
10244                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10245         }
10246         if (resultDetails != NULL) {
10247             gameInfo.result = result;
10248             gameInfo.resultDetails = StrSave(resultDetails);
10249
10250             /* display last move only if game was not loaded from file */
10251             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10252                 DisplayMove(currentMove - 1);
10253
10254             if (forwardMostMove != 0) {
10255                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10256                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10257                                                                 ) {
10258                     if (*appData.saveGameFile != NULLCHAR) {
10259                         SaveGameToFile(appData.saveGameFile, TRUE);
10260                     } else if (appData.autoSaveGames) {
10261                         AutoSaveGame();
10262                     }
10263                     if (*appData.savePositionFile != NULLCHAR) {
10264                         SavePositionToFile(appData.savePositionFile);
10265                     }
10266                 }
10267             }
10268
10269             /* Tell program how game ended in case it is learning */
10270             /* [HGM] Moved this to after saving the PGN, just in case */
10271             /* engine died and we got here through time loss. In that */
10272             /* case we will get a fatal error writing the pipe, which */
10273             /* would otherwise lose us the PGN.                       */
10274             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10275             /* output during GameEnds should never be fatal anymore   */
10276             if (gameMode == MachinePlaysWhite ||
10277                 gameMode == MachinePlaysBlack ||
10278                 gameMode == TwoMachinesPlay ||
10279                 gameMode == IcsPlayingWhite ||
10280                 gameMode == IcsPlayingBlack ||
10281                 gameMode == BeginningOfGame) {
10282                 char buf[MSG_SIZ];
10283                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10284                         resultDetails);
10285                 if (first.pr != NoProc) {
10286                     SendToProgram(buf, &first);
10287                 }
10288                 if (second.pr != NoProc &&
10289                     gameMode == TwoMachinesPlay) {
10290                     SendToProgram(buf, &second);
10291                 }
10292             }
10293         }
10294
10295         if (appData.icsActive) {
10296             if (appData.quietPlay &&
10297                 (gameMode == IcsPlayingWhite ||
10298                  gameMode == IcsPlayingBlack)) {
10299                 SendToICS(ics_prefix);
10300                 SendToICS("set shout 1\n");
10301             }
10302             nextGameMode = IcsIdle;
10303             ics_user_moved = FALSE;
10304             /* clean up premove.  It's ugly when the game has ended and the
10305              * premove highlights are still on the board.
10306              */
10307             if (gotPremove) {
10308               gotPremove = FALSE;
10309               ClearPremoveHighlights();
10310               DrawPosition(FALSE, boards[currentMove]);
10311             }
10312             if (whosays == GE_ICS) {
10313                 switch (result) {
10314                 case WhiteWins:
10315                     if (gameMode == IcsPlayingWhite)
10316                         PlayIcsWinSound();
10317                     else if(gameMode == IcsPlayingBlack)
10318                         PlayIcsLossSound();
10319                     break;
10320                 case BlackWins:
10321                     if (gameMode == IcsPlayingBlack)
10322                         PlayIcsWinSound();
10323                     else if(gameMode == IcsPlayingWhite)
10324                         PlayIcsLossSound();
10325                     break;
10326                 case GameIsDrawn:
10327                     PlayIcsDrawSound();
10328                     break;
10329                 default:
10330                     PlayIcsUnfinishedSound();
10331                 }
10332             }
10333         } else if (gameMode == EditGame ||
10334                    gameMode == PlayFromGameFile ||
10335                    gameMode == AnalyzeMode ||
10336                    gameMode == AnalyzeFile) {
10337             nextGameMode = gameMode;
10338         } else {
10339             nextGameMode = EndOfGame;
10340         }
10341         pausing = FALSE;
10342         ModeHighlight();
10343     } else {
10344         nextGameMode = gameMode;
10345     }
10346
10347     if (appData.noChessProgram) {
10348         gameMode = nextGameMode;
10349         ModeHighlight();
10350         endingGame = 0; /* [HGM] crash */
10351         return;
10352     }
10353
10354     if (first.reuse) {
10355         /* Put first chess program into idle state */
10356         if (first.pr != NoProc &&
10357             (gameMode == MachinePlaysWhite ||
10358              gameMode == MachinePlaysBlack ||
10359              gameMode == TwoMachinesPlay ||
10360              gameMode == IcsPlayingWhite ||
10361              gameMode == IcsPlayingBlack ||
10362              gameMode == BeginningOfGame)) {
10363             SendToProgram("force\n", &first);
10364             if (first.usePing) {
10365               char buf[MSG_SIZ];
10366               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10367               SendToProgram(buf, &first);
10368             }
10369         }
10370     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10371         /* Kill off first chess program */
10372         if (first.isr != NULL)
10373           RemoveInputSource(first.isr);
10374         first.isr = NULL;
10375
10376         if (first.pr != NoProc) {
10377             ExitAnalyzeMode();
10378             DoSleep( appData.delayBeforeQuit );
10379             SendToProgram("quit\n", &first);
10380             DoSleep( appData.delayAfterQuit );
10381             DestroyChildProcess(first.pr, first.useSigterm);
10382         }
10383         first.pr = NoProc;
10384     }
10385     if (second.reuse) {
10386         /* Put second chess program into idle state */
10387         if (second.pr != NoProc &&
10388             gameMode == TwoMachinesPlay) {
10389             SendToProgram("force\n", &second);
10390             if (second.usePing) {
10391               char buf[MSG_SIZ];
10392               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10393               SendToProgram(buf, &second);
10394             }
10395         }
10396     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10397         /* Kill off second chess program */
10398         if (second.isr != NULL)
10399           RemoveInputSource(second.isr);
10400         second.isr = NULL;
10401
10402         if (second.pr != NoProc) {
10403             DoSleep( appData.delayBeforeQuit );
10404             SendToProgram("quit\n", &second);
10405             DoSleep( appData.delayAfterQuit );
10406             DestroyChildProcess(second.pr, second.useSigterm);
10407         }
10408         second.pr = NoProc;
10409     }
10410
10411     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10412         char resChar = '=';
10413         switch (result) {
10414         case WhiteWins:
10415           resChar = '+';
10416           if (first.twoMachinesColor[0] == 'w') {
10417             first.matchWins++;
10418           } else {
10419             second.matchWins++;
10420           }
10421           break;
10422         case BlackWins:
10423           resChar = '-';
10424           if (first.twoMachinesColor[0] == 'b') {
10425             first.matchWins++;
10426           } else {
10427             second.matchWins++;
10428           }
10429           break;
10430         case GameUnfinished:
10431           resChar = ' ';
10432         default:
10433           break;
10434         }
10435
10436         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10437         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10438             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10439             ReserveGame(nextGame, resChar); // sets nextGame
10440             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10441             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10442         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10443
10444         if (nextGame <= appData.matchGames && !abortMatch) {
10445             gameMode = nextGameMode;
10446             matchGame = nextGame; // this will be overruled in tourney mode!
10447             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10448             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10449             endingGame = 0; /* [HGM] crash */
10450             return;
10451         } else {
10452             gameMode = nextGameMode;
10453             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10454                      first.tidy, second.tidy,
10455                      first.matchWins, second.matchWins,
10456                      appData.matchGames - (first.matchWins + second.matchWins));
10457             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10458             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10459             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10460             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10461                 first.twoMachinesColor = "black\n";
10462                 second.twoMachinesColor = "white\n";
10463             } else {
10464                 first.twoMachinesColor = "white\n";
10465                 second.twoMachinesColor = "black\n";
10466             }
10467         }
10468     }
10469     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10470         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10471       ExitAnalyzeMode();
10472     gameMode = nextGameMode;
10473     ModeHighlight();
10474     endingGame = 0;  /* [HGM] crash */
10475     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10476         if(matchMode == TRUE) { // match through command line: exit with or without popup
10477             if(ranking) {
10478                 ToNrEvent(forwardMostMove);
10479                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10480                 else ExitEvent(0);
10481             } else DisplayFatalError(buf, 0, 0);
10482         } else { // match through menu; just stop, with or without popup
10483             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10484             ModeHighlight();
10485             if(ranking){
10486                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10487             } else DisplayNote(buf);
10488       }
10489       if(ranking) free(ranking);
10490     }
10491 }
10492
10493 /* Assumes program was just initialized (initString sent).
10494    Leaves program in force mode. */
10495 void
10496 FeedMovesToProgram (ChessProgramState *cps, int upto)
10497 {
10498     int i;
10499
10500     if (appData.debugMode)
10501       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10502               startedFromSetupPosition ? "position and " : "",
10503               backwardMostMove, upto, cps->which);
10504     if(currentlyInitializedVariant != gameInfo.variant) {
10505       char buf[MSG_SIZ];
10506         // [HGM] variantswitch: make engine aware of new variant
10507         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10508                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10509         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10510         SendToProgram(buf, cps);
10511         currentlyInitializedVariant = gameInfo.variant;
10512     }
10513     SendToProgram("force\n", cps);
10514     if (startedFromSetupPosition) {
10515         SendBoard(cps, backwardMostMove);
10516     if (appData.debugMode) {
10517         fprintf(debugFP, "feedMoves\n");
10518     }
10519     }
10520     for (i = backwardMostMove; i < upto; i++) {
10521         SendMoveToProgram(i, cps);
10522     }
10523 }
10524
10525
10526 int
10527 ResurrectChessProgram ()
10528 {
10529      /* The chess program may have exited.
10530         If so, restart it and feed it all the moves made so far. */
10531     static int doInit = 0;
10532
10533     if (appData.noChessProgram) return 1;
10534
10535     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10536         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10537         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10538         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10539     } else {
10540         if (first.pr != NoProc) return 1;
10541         StartChessProgram(&first);
10542     }
10543     InitChessProgram(&first, FALSE);
10544     FeedMovesToProgram(&first, currentMove);
10545
10546     if (!first.sendTime) {
10547         /* can't tell gnuchess what its clock should read,
10548            so we bow to its notion. */
10549         ResetClocks();
10550         timeRemaining[0][currentMove] = whiteTimeRemaining;
10551         timeRemaining[1][currentMove] = blackTimeRemaining;
10552     }
10553
10554     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10555                 appData.icsEngineAnalyze) && first.analysisSupport) {
10556       SendToProgram("analyze\n", &first);
10557       first.analyzing = TRUE;
10558     }
10559     return 1;
10560 }
10561
10562 /*
10563  * Button procedures
10564  */
10565 void
10566 Reset (int redraw, int init)
10567 {
10568     int i;
10569
10570     if (appData.debugMode) {
10571         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10572                 redraw, init, gameMode);
10573     }
10574     CleanupTail(); // [HGM] vari: delete any stored variations
10575     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10576     pausing = pauseExamInvalid = FALSE;
10577     startedFromSetupPosition = blackPlaysFirst = FALSE;
10578     firstMove = TRUE;
10579     whiteFlag = blackFlag = FALSE;
10580     userOfferedDraw = FALSE;
10581     hintRequested = bookRequested = FALSE;
10582     first.maybeThinking = FALSE;
10583     second.maybeThinking = FALSE;
10584     first.bookSuspend = FALSE; // [HGM] book
10585     second.bookSuspend = FALSE;
10586     thinkOutput[0] = NULLCHAR;
10587     lastHint[0] = NULLCHAR;
10588     ClearGameInfo(&gameInfo);
10589     gameInfo.variant = StringToVariant(appData.variant);
10590     ics_user_moved = ics_clock_paused = FALSE;
10591     ics_getting_history = H_FALSE;
10592     ics_gamenum = -1;
10593     white_holding[0] = black_holding[0] = NULLCHAR;
10594     ClearProgramStats();
10595     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10596
10597     ResetFrontEnd();
10598     ClearHighlights();
10599     flipView = appData.flipView;
10600     ClearPremoveHighlights();
10601     gotPremove = FALSE;
10602     alarmSounded = FALSE;
10603
10604     GameEnds(EndOfFile, NULL, GE_PLAYER);
10605     if(appData.serverMovesName != NULL) {
10606         /* [HGM] prepare to make moves file for broadcasting */
10607         clock_t t = clock();
10608         if(serverMoves != NULL) fclose(serverMoves);
10609         serverMoves = fopen(appData.serverMovesName, "r");
10610         if(serverMoves != NULL) {
10611             fclose(serverMoves);
10612             /* delay 15 sec before overwriting, so all clients can see end */
10613             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10614         }
10615         serverMoves = fopen(appData.serverMovesName, "w");
10616     }
10617
10618     ExitAnalyzeMode();
10619     gameMode = BeginningOfGame;
10620     ModeHighlight();
10621     if(appData.icsActive) gameInfo.variant = VariantNormal;
10622     currentMove = forwardMostMove = backwardMostMove = 0;
10623     MarkTargetSquares(1);
10624     InitPosition(redraw);
10625     for (i = 0; i < MAX_MOVES; i++) {
10626         if (commentList[i] != NULL) {
10627             free(commentList[i]);
10628             commentList[i] = NULL;
10629         }
10630     }
10631     ResetClocks();
10632     timeRemaining[0][0] = whiteTimeRemaining;
10633     timeRemaining[1][0] = blackTimeRemaining;
10634
10635     if (first.pr == NoProc) {
10636         StartChessProgram(&first);
10637     }
10638     if (init) {
10639             InitChessProgram(&first, startedFromSetupPosition);
10640     }
10641     DisplayTitle("");
10642     DisplayMessage("", "");
10643     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10644     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10645 }
10646
10647 void
10648 AutoPlayGameLoop ()
10649 {
10650     for (;;) {
10651         if (!AutoPlayOneMove())
10652           return;
10653         if (matchMode || appData.timeDelay == 0)
10654           continue;
10655         if (appData.timeDelay < 0)
10656           return;
10657         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10658         break;
10659     }
10660 }
10661
10662
10663 int
10664 AutoPlayOneMove ()
10665 {
10666     int fromX, fromY, toX, toY;
10667
10668     if (appData.debugMode) {
10669       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10670     }
10671
10672     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10673       return FALSE;
10674
10675     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10676       pvInfoList[currentMove].depth = programStats.depth;
10677       pvInfoList[currentMove].score = programStats.score;
10678       pvInfoList[currentMove].time  = 0;
10679       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10680     }
10681
10682     if (currentMove >= forwardMostMove) {
10683       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10684 //      gameMode = EndOfGame;
10685 //      ModeHighlight();
10686
10687       /* [AS] Clear current move marker at the end of a game */
10688       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10689
10690       return FALSE;
10691     }
10692
10693     toX = moveList[currentMove][2] - AAA;
10694     toY = moveList[currentMove][3] - ONE;
10695
10696     if (moveList[currentMove][1] == '@') {
10697         if (appData.highlightLastMove) {
10698             SetHighlights(-1, -1, toX, toY);
10699         }
10700     } else {
10701         fromX = moveList[currentMove][0] - AAA;
10702         fromY = moveList[currentMove][1] - ONE;
10703
10704         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10705
10706         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10707
10708         if (appData.highlightLastMove) {
10709             SetHighlights(fromX, fromY, toX, toY);
10710         }
10711     }
10712     DisplayMove(currentMove);
10713     SendMoveToProgram(currentMove++, &first);
10714     DisplayBothClocks();
10715     DrawPosition(FALSE, boards[currentMove]);
10716     // [HGM] PV info: always display, routine tests if empty
10717     DisplayComment(currentMove - 1, commentList[currentMove]);
10718     return TRUE;
10719 }
10720
10721
10722 int
10723 LoadGameOneMove (ChessMove readAhead)
10724 {
10725     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10726     char promoChar = NULLCHAR;
10727     ChessMove moveType;
10728     char move[MSG_SIZ];
10729     char *p, *q;
10730
10731     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10732         gameMode != AnalyzeMode && gameMode != Training) {
10733         gameFileFP = NULL;
10734         return FALSE;
10735     }
10736
10737     yyboardindex = forwardMostMove;
10738     if (readAhead != EndOfFile) {
10739       moveType = readAhead;
10740     } else {
10741       if (gameFileFP == NULL)
10742           return FALSE;
10743       moveType = (ChessMove) Myylex();
10744     }
10745
10746     done = FALSE;
10747     switch (moveType) {
10748       case Comment:
10749         if (appData.debugMode)
10750           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10751         p = yy_text;
10752
10753         /* append the comment but don't display it */
10754         AppendComment(currentMove, p, FALSE);
10755         return TRUE;
10756
10757       case WhiteCapturesEnPassant:
10758       case BlackCapturesEnPassant:
10759       case WhitePromotion:
10760       case BlackPromotion:
10761       case WhiteNonPromotion:
10762       case BlackNonPromotion:
10763       case NormalMove:
10764       case WhiteKingSideCastle:
10765       case WhiteQueenSideCastle:
10766       case BlackKingSideCastle:
10767       case BlackQueenSideCastle:
10768       case WhiteKingSideCastleWild:
10769       case WhiteQueenSideCastleWild:
10770       case BlackKingSideCastleWild:
10771       case BlackQueenSideCastleWild:
10772       /* PUSH Fabien */
10773       case WhiteHSideCastleFR:
10774       case WhiteASideCastleFR:
10775       case BlackHSideCastleFR:
10776       case BlackASideCastleFR:
10777       /* POP Fabien */
10778         if (appData.debugMode)
10779           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10780         fromX = currentMoveString[0] - AAA;
10781         fromY = currentMoveString[1] - ONE;
10782         toX = currentMoveString[2] - AAA;
10783         toY = currentMoveString[3] - ONE;
10784         promoChar = currentMoveString[4];
10785         break;
10786
10787       case WhiteDrop:
10788       case BlackDrop:
10789         if (appData.debugMode)
10790           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10791         fromX = moveType == WhiteDrop ?
10792           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10793         (int) CharToPiece(ToLower(currentMoveString[0]));
10794         fromY = DROP_RANK;
10795         toX = currentMoveString[2] - AAA;
10796         toY = currentMoveString[3] - ONE;
10797         break;
10798
10799       case WhiteWins:
10800       case BlackWins:
10801       case GameIsDrawn:
10802       case GameUnfinished:
10803         if (appData.debugMode)
10804           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10805         p = strchr(yy_text, '{');
10806         if (p == NULL) p = strchr(yy_text, '(');
10807         if (p == NULL) {
10808             p = yy_text;
10809             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10810         } else {
10811             q = strchr(p, *p == '{' ? '}' : ')');
10812             if (q != NULL) *q = NULLCHAR;
10813             p++;
10814         }
10815         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10816         GameEnds(moveType, p, GE_FILE);
10817         done = TRUE;
10818         if (cmailMsgLoaded) {
10819             ClearHighlights();
10820             flipView = WhiteOnMove(currentMove);
10821             if (moveType == GameUnfinished) flipView = !flipView;
10822             if (appData.debugMode)
10823               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10824         }
10825         break;
10826
10827       case EndOfFile:
10828         if (appData.debugMode)
10829           fprintf(debugFP, "Parser hit end of file\n");
10830         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10831           case MT_NONE:
10832           case MT_CHECK:
10833             break;
10834           case MT_CHECKMATE:
10835           case MT_STAINMATE:
10836             if (WhiteOnMove(currentMove)) {
10837                 GameEnds(BlackWins, "Black mates", GE_FILE);
10838             } else {
10839                 GameEnds(WhiteWins, "White mates", GE_FILE);
10840             }
10841             break;
10842           case MT_STALEMATE:
10843             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10844             break;
10845         }
10846         done = TRUE;
10847         break;
10848
10849       case MoveNumberOne:
10850         if (lastLoadGameStart == GNUChessGame) {
10851             /* GNUChessGames have numbers, but they aren't move numbers */
10852             if (appData.debugMode)
10853               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10854                       yy_text, (int) moveType);
10855             return LoadGameOneMove(EndOfFile); /* tail recursion */
10856         }
10857         /* else fall thru */
10858
10859       case XBoardGame:
10860       case GNUChessGame:
10861       case PGNTag:
10862         /* Reached start of next game in file */
10863         if (appData.debugMode)
10864           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10865         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10866           case MT_NONE:
10867           case MT_CHECK:
10868             break;
10869           case MT_CHECKMATE:
10870           case MT_STAINMATE:
10871             if (WhiteOnMove(currentMove)) {
10872                 GameEnds(BlackWins, "Black mates", GE_FILE);
10873             } else {
10874                 GameEnds(WhiteWins, "White mates", GE_FILE);
10875             }
10876             break;
10877           case MT_STALEMATE:
10878             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10879             break;
10880         }
10881         done = TRUE;
10882         break;
10883
10884       case PositionDiagram:     /* should not happen; ignore */
10885       case ElapsedTime:         /* ignore */
10886       case NAG:                 /* ignore */
10887         if (appData.debugMode)
10888           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10889                   yy_text, (int) moveType);
10890         return LoadGameOneMove(EndOfFile); /* tail recursion */
10891
10892       case IllegalMove:
10893         if (appData.testLegality) {
10894             if (appData.debugMode)
10895               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10896             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10897                     (forwardMostMove / 2) + 1,
10898                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10899             DisplayError(move, 0);
10900             done = TRUE;
10901         } else {
10902             if (appData.debugMode)
10903               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10904                       yy_text, currentMoveString);
10905             fromX = currentMoveString[0] - AAA;
10906             fromY = currentMoveString[1] - ONE;
10907             toX = currentMoveString[2] - AAA;
10908             toY = currentMoveString[3] - ONE;
10909             promoChar = currentMoveString[4];
10910         }
10911         break;
10912
10913       case AmbiguousMove:
10914         if (appData.debugMode)
10915           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10916         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10917                 (forwardMostMove / 2) + 1,
10918                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10919         DisplayError(move, 0);
10920         done = TRUE;
10921         break;
10922
10923       default:
10924       case ImpossibleMove:
10925         if (appData.debugMode)
10926           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10927         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10928                 (forwardMostMove / 2) + 1,
10929                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10930         DisplayError(move, 0);
10931         done = TRUE;
10932         break;
10933     }
10934
10935     if (done) {
10936         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10937             DrawPosition(FALSE, boards[currentMove]);
10938             DisplayBothClocks();
10939             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10940               DisplayComment(currentMove - 1, commentList[currentMove]);
10941         }
10942         (void) StopLoadGameTimer();
10943         gameFileFP = NULL;
10944         cmailOldMove = forwardMostMove;
10945         return FALSE;
10946     } else {
10947         /* currentMoveString is set as a side-effect of yylex */
10948
10949         thinkOutput[0] = NULLCHAR;
10950         MakeMove(fromX, fromY, toX, toY, promoChar);
10951         currentMove = forwardMostMove;
10952         return TRUE;
10953     }
10954 }
10955
10956 /* Load the nth game from the given file */
10957 int
10958 LoadGameFromFile (char *filename, int n, char *title, int useList)
10959 {
10960     FILE *f;
10961     char buf[MSG_SIZ];
10962
10963     if (strcmp(filename, "-") == 0) {
10964         f = stdin;
10965         title = "stdin";
10966     } else {
10967         f = fopen(filename, "rb");
10968         if (f == NULL) {
10969           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10970             DisplayError(buf, errno);
10971             return FALSE;
10972         }
10973     }
10974     if (fseek(f, 0, 0) == -1) {
10975         /* f is not seekable; probably a pipe */
10976         useList = FALSE;
10977     }
10978     if (useList && n == 0) {
10979         int error = GameListBuild(f);
10980         if (error) {
10981             DisplayError(_("Cannot build game list"), error);
10982         } else if (!ListEmpty(&gameList) &&
10983                    ((ListGame *) gameList.tailPred)->number > 1) {
10984             GameListPopUp(f, title);
10985             return TRUE;
10986         }
10987         GameListDestroy();
10988         n = 1;
10989     }
10990     if (n == 0) n = 1;
10991     return LoadGame(f, n, title, FALSE);
10992 }
10993
10994
10995 void
10996 MakeRegisteredMove ()
10997 {
10998     int fromX, fromY, toX, toY;
10999     char promoChar;
11000     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11001         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11002           case CMAIL_MOVE:
11003           case CMAIL_DRAW:
11004             if (appData.debugMode)
11005               fprintf(debugFP, "Restoring %s for game %d\n",
11006                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11007
11008             thinkOutput[0] = NULLCHAR;
11009             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11010             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11011             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11012             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11013             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11014             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11015             MakeMove(fromX, fromY, toX, toY, promoChar);
11016             ShowMove(fromX, fromY, toX, toY);
11017
11018             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11019               case MT_NONE:
11020               case MT_CHECK:
11021                 break;
11022
11023               case MT_CHECKMATE:
11024               case MT_STAINMATE:
11025                 if (WhiteOnMove(currentMove)) {
11026                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11027                 } else {
11028                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11029                 }
11030                 break;
11031
11032               case MT_STALEMATE:
11033                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11034                 break;
11035             }
11036
11037             break;
11038
11039           case CMAIL_RESIGN:
11040             if (WhiteOnMove(currentMove)) {
11041                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11042             } else {
11043                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11044             }
11045             break;
11046
11047           case CMAIL_ACCEPT:
11048             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11049             break;
11050
11051           default:
11052             break;
11053         }
11054     }
11055
11056     return;
11057 }
11058
11059 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11060 int
11061 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11062 {
11063     int retVal;
11064
11065     if (gameNumber > nCmailGames) {
11066         DisplayError(_("No more games in this message"), 0);
11067         return FALSE;
11068     }
11069     if (f == lastLoadGameFP) {
11070         int offset = gameNumber - lastLoadGameNumber;
11071         if (offset == 0) {
11072             cmailMsg[0] = NULLCHAR;
11073             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11074                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11075                 nCmailMovesRegistered--;
11076             }
11077             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11078             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11079                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11080             }
11081         } else {
11082             if (! RegisterMove()) return FALSE;
11083         }
11084     }
11085
11086     retVal = LoadGame(f, gameNumber, title, useList);
11087
11088     /* Make move registered during previous look at this game, if any */
11089     MakeRegisteredMove();
11090
11091     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11092         commentList[currentMove]
11093           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11094         DisplayComment(currentMove - 1, commentList[currentMove]);
11095     }
11096
11097     return retVal;
11098 }
11099
11100 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11101 int
11102 ReloadGame (int offset)
11103 {
11104     int gameNumber = lastLoadGameNumber + offset;
11105     if (lastLoadGameFP == NULL) {
11106         DisplayError(_("No game has been loaded yet"), 0);
11107         return FALSE;
11108     }
11109     if (gameNumber <= 0) {
11110         DisplayError(_("Can't back up any further"), 0);
11111         return FALSE;
11112     }
11113     if (cmailMsgLoaded) {
11114         return CmailLoadGame(lastLoadGameFP, gameNumber,
11115                              lastLoadGameTitle, lastLoadGameUseList);
11116     } else {
11117         return LoadGame(lastLoadGameFP, gameNumber,
11118                         lastLoadGameTitle, lastLoadGameUseList);
11119     }
11120 }
11121
11122 int keys[EmptySquare+1];
11123
11124 int
11125 PositionMatches (Board b1, Board b2)
11126 {
11127     int r, f, sum=0;
11128     switch(appData.searchMode) {
11129         case 1: return CompareWithRights(b1, b2);
11130         case 2:
11131             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11132                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11133             }
11134             return TRUE;
11135         case 3:
11136             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11137               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11138                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11139             }
11140             return sum==0;
11141         case 4:
11142             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11143                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11144             }
11145             return sum==0;
11146     }
11147     return TRUE;
11148 }
11149
11150 #define Q_PROMO  4
11151 #define Q_EP     3
11152 #define Q_BCASTL 2
11153 #define Q_WCASTL 1
11154
11155 int pieceList[256], quickBoard[256];
11156 ChessSquare pieceType[256] = { EmptySquare };
11157 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11158 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11159 int soughtTotal, turn;
11160 Boolean epOK, flipSearch;
11161
11162 typedef struct {
11163     unsigned char piece, to;
11164 } Move;
11165
11166 #define DSIZE (250000)
11167
11168 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11169 Move *moveDatabase = initialSpace;
11170 unsigned int movePtr, dataSize = DSIZE;
11171
11172 int
11173 MakePieceList (Board board, int *counts)
11174 {
11175     int r, f, n=Q_PROMO, total=0;
11176     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11177     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11178         int sq = f + (r<<4);
11179         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11180             quickBoard[sq] = ++n;
11181             pieceList[n] = sq;
11182             pieceType[n] = board[r][f];
11183             counts[board[r][f]]++;
11184             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11185             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11186             total++;
11187         }
11188     }
11189     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11190     return total;
11191 }
11192
11193 void
11194 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11195 {
11196     int sq = fromX + (fromY<<4);
11197     int piece = quickBoard[sq];
11198     quickBoard[sq] = 0;
11199     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11200     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11201         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11202         moveDatabase[movePtr++].piece = Q_WCASTL;
11203         quickBoard[sq] = piece;
11204         piece = quickBoard[from]; quickBoard[from] = 0;
11205         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11206     } else
11207     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11208         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11209         moveDatabase[movePtr++].piece = Q_BCASTL;
11210         quickBoard[sq] = piece;
11211         piece = quickBoard[from]; quickBoard[from] = 0;
11212         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11213     } else
11214     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11215         quickBoard[(fromY<<4)+toX] = 0;
11216         moveDatabase[movePtr].piece = Q_EP;
11217         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11218         moveDatabase[movePtr].to = sq;
11219     } else
11220     if(promoPiece != pieceType[piece]) {
11221         moveDatabase[movePtr++].piece = Q_PROMO;
11222         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11223     }
11224     moveDatabase[movePtr].piece = piece;
11225     quickBoard[sq] = piece;
11226     movePtr++;
11227 }
11228
11229 int
11230 PackGame (Board board)
11231 {
11232     Move *newSpace = NULL;
11233     moveDatabase[movePtr].piece = 0; // terminate previous game
11234     if(movePtr > dataSize) {
11235         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11236         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11237         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11238         if(newSpace) {
11239             int i;
11240             Move *p = moveDatabase, *q = newSpace;
11241             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11242             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11243             moveDatabase = newSpace;
11244         } else { // calloc failed, we must be out of memory. Too bad...
11245             dataSize = 0; // prevent calloc events for all subsequent games
11246             return 0;     // and signal this one isn't cached
11247         }
11248     }
11249     movePtr++;
11250     MakePieceList(board, counts);
11251     return movePtr;
11252 }
11253
11254 int
11255 QuickCompare (Board board, int *minCounts, int *maxCounts)
11256 {   // compare according to search mode
11257     int r, f;
11258     switch(appData.searchMode)
11259     {
11260       case 1: // exact position match
11261         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11262         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11263             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11264         }
11265         break;
11266       case 2: // can have extra material on empty squares
11267         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11268             if(board[r][f] == EmptySquare) continue;
11269             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11270         }
11271         break;
11272       case 3: // material with exact Pawn structure
11273         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11274             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11275             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11276         } // fall through to material comparison
11277       case 4: // exact material
11278         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11279         break;
11280       case 6: // material range with given imbalance
11281         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11282         // fall through to range comparison
11283       case 5: // material range
11284         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11285     }
11286     return TRUE;
11287 }
11288
11289 int
11290 QuickScan (Board board, Move *move)
11291 {   // reconstruct game,and compare all positions in it
11292     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11293     do {
11294         int piece = move->piece;
11295         int to = move->to, from = pieceList[piece];
11296         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11297           if(!piece) return -1;
11298           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11299             piece = (++move)->piece;
11300             from = pieceList[piece];
11301             counts[pieceType[piece]]--;
11302             pieceType[piece] = (ChessSquare) move->to;
11303             counts[move->to]++;
11304           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11305             counts[pieceType[quickBoard[to]]]--;
11306             quickBoard[to] = 0; total--;
11307             move++;
11308             continue;
11309           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11310             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11311             from  = pieceList[piece]; // so this must be King
11312             quickBoard[from] = 0;
11313             quickBoard[to] = piece;
11314             pieceList[piece] = to;
11315             move++;
11316             continue;
11317           }
11318         }
11319         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11320         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11321         quickBoard[from] = 0;
11322         quickBoard[to] = piece;
11323         pieceList[piece] = to;
11324         cnt++; turn ^= 3;
11325         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11326            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11327            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11328                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11329           ) {
11330             static int lastCounts[EmptySquare+1];
11331             int i;
11332             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11333             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11334         } else stretch = 0;
11335         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11336         move++;
11337     } while(1);
11338 }
11339
11340 void
11341 InitSearch ()
11342 {
11343     int r, f;
11344     flipSearch = FALSE;
11345     CopyBoard(soughtBoard, boards[currentMove]);
11346     soughtTotal = MakePieceList(soughtBoard, maxSought);
11347     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11348     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11349     CopyBoard(reverseBoard, boards[currentMove]);
11350     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11351         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11352         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11353         reverseBoard[r][f] = piece;
11354     }
11355     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11356     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11357     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11358                  || (boards[currentMove][CASTLING][2] == NoRights || 
11359                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11360                  && (boards[currentMove][CASTLING][5] == NoRights || 
11361                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11362       ) {
11363         flipSearch = TRUE;
11364         CopyBoard(flipBoard, soughtBoard);
11365         CopyBoard(rotateBoard, reverseBoard);
11366         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11367             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11368             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11369         }
11370     }
11371     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11372     if(appData.searchMode >= 5) {
11373         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11374         MakePieceList(soughtBoard, minSought);
11375         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11376     }
11377     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11378         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11379 }
11380
11381 GameInfo dummyInfo;
11382
11383 int
11384 GameContainsPosition (FILE *f, ListGame *lg)
11385 {
11386     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11387     int fromX, fromY, toX, toY;
11388     char promoChar;
11389     static int initDone=FALSE;
11390
11391     // weed out games based on numerical tag comparison
11392     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11393     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11394     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11395     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11396     if(!initDone) {
11397         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11398         initDone = TRUE;
11399     }
11400     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11401     else CopyBoard(boards[scratch], initialPosition); // default start position
11402     if(lg->moves) {
11403         turn = btm + 1;
11404         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11405         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11406     }
11407     if(btm) plyNr++;
11408     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11409     fseek(f, lg->offset, 0);
11410     yynewfile(f);
11411     while(1) {
11412         yyboardindex = scratch;
11413         quickFlag = plyNr+1;
11414         next = Myylex();
11415         quickFlag = 0;
11416         switch(next) {
11417             case PGNTag:
11418                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11419             default:
11420                 continue;
11421
11422             case XBoardGame:
11423             case GNUChessGame:
11424                 if(plyNr) return -1; // after we have seen moves, this is for new game
11425               continue;
11426
11427             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11428             case ImpossibleMove:
11429             case WhiteWins: // game ends here with these four
11430             case BlackWins:
11431             case GameIsDrawn:
11432             case GameUnfinished:
11433                 return -1;
11434
11435             case IllegalMove:
11436                 if(appData.testLegality) return -1;
11437             case WhiteCapturesEnPassant:
11438             case BlackCapturesEnPassant:
11439             case WhitePromotion:
11440             case BlackPromotion:
11441             case WhiteNonPromotion:
11442             case BlackNonPromotion:
11443             case NormalMove:
11444             case WhiteKingSideCastle:
11445             case WhiteQueenSideCastle:
11446             case BlackKingSideCastle:
11447             case BlackQueenSideCastle:
11448             case WhiteKingSideCastleWild:
11449             case WhiteQueenSideCastleWild:
11450             case BlackKingSideCastleWild:
11451             case BlackQueenSideCastleWild:
11452             case WhiteHSideCastleFR:
11453             case WhiteASideCastleFR:
11454             case BlackHSideCastleFR:
11455             case BlackASideCastleFR:
11456                 fromX = currentMoveString[0] - AAA;
11457                 fromY = currentMoveString[1] - ONE;
11458                 toX = currentMoveString[2] - AAA;
11459                 toY = currentMoveString[3] - ONE;
11460                 promoChar = currentMoveString[4];
11461                 break;
11462             case WhiteDrop:
11463             case BlackDrop:
11464                 fromX = next == WhiteDrop ?
11465                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11466                   (int) CharToPiece(ToLower(currentMoveString[0]));
11467                 fromY = DROP_RANK;
11468                 toX = currentMoveString[2] - AAA;
11469                 toY = currentMoveString[3] - ONE;
11470                 promoChar = 0;
11471                 break;
11472         }
11473         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11474         plyNr++;
11475         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11476         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11477         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11478         if(appData.findMirror) {
11479             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11480             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11481         }
11482     }
11483 }
11484
11485 /* Load the nth game from open file f */
11486 int
11487 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11488 {
11489     ChessMove cm;
11490     char buf[MSG_SIZ];
11491     int gn = gameNumber;
11492     ListGame *lg = NULL;
11493     int numPGNTags = 0;
11494     int err, pos = -1;
11495     GameMode oldGameMode;
11496     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11497
11498     if (appData.debugMode)
11499         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11500
11501     if (gameMode == Training )
11502         SetTrainingModeOff();
11503
11504     oldGameMode = gameMode;
11505     if (gameMode != BeginningOfGame) {
11506       Reset(FALSE, TRUE);
11507     }
11508
11509     gameFileFP = f;
11510     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11511         fclose(lastLoadGameFP);
11512     }
11513
11514     if (useList) {
11515         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11516
11517         if (lg) {
11518             fseek(f, lg->offset, 0);
11519             GameListHighlight(gameNumber);
11520             pos = lg->position;
11521             gn = 1;
11522         }
11523         else {
11524             DisplayError(_("Game number out of range"), 0);
11525             return FALSE;
11526         }
11527     } else {
11528         GameListDestroy();
11529         if (fseek(f, 0, 0) == -1) {
11530             if (f == lastLoadGameFP ?
11531                 gameNumber == lastLoadGameNumber + 1 :
11532                 gameNumber == 1) {
11533                 gn = 1;
11534             } else {
11535                 DisplayError(_("Can't seek on game file"), 0);
11536                 return FALSE;
11537             }
11538         }
11539     }
11540     lastLoadGameFP = f;
11541     lastLoadGameNumber = gameNumber;
11542     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11543     lastLoadGameUseList = useList;
11544
11545     yynewfile(f);
11546
11547     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11548       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11549                 lg->gameInfo.black);
11550             DisplayTitle(buf);
11551     } else if (*title != NULLCHAR) {
11552         if (gameNumber > 1) {
11553           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11554             DisplayTitle(buf);
11555         } else {
11556             DisplayTitle(title);
11557         }
11558     }
11559
11560     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11561         gameMode = PlayFromGameFile;
11562         ModeHighlight();
11563     }
11564
11565     currentMove = forwardMostMove = backwardMostMove = 0;
11566     CopyBoard(boards[0], initialPosition);
11567     StopClocks();
11568
11569     /*
11570      * Skip the first gn-1 games in the file.
11571      * Also skip over anything that precedes an identifiable
11572      * start of game marker, to avoid being confused by
11573      * garbage at the start of the file.  Currently
11574      * recognized start of game markers are the move number "1",
11575      * the pattern "gnuchess .* game", the pattern
11576      * "^[#;%] [^ ]* game file", and a PGN tag block.
11577      * A game that starts with one of the latter two patterns
11578      * will also have a move number 1, possibly
11579      * following a position diagram.
11580      * 5-4-02: Let's try being more lenient and allowing a game to
11581      * start with an unnumbered move.  Does that break anything?
11582      */
11583     cm = lastLoadGameStart = EndOfFile;
11584     while (gn > 0) {
11585         yyboardindex = forwardMostMove;
11586         cm = (ChessMove) Myylex();
11587         switch (cm) {
11588           case EndOfFile:
11589             if (cmailMsgLoaded) {
11590                 nCmailGames = CMAIL_MAX_GAMES - gn;
11591             } else {
11592                 Reset(TRUE, TRUE);
11593                 DisplayError(_("Game not found in file"), 0);
11594             }
11595             return FALSE;
11596
11597           case GNUChessGame:
11598           case XBoardGame:
11599             gn--;
11600             lastLoadGameStart = cm;
11601             break;
11602
11603           case MoveNumberOne:
11604             switch (lastLoadGameStart) {
11605               case GNUChessGame:
11606               case XBoardGame:
11607               case PGNTag:
11608                 break;
11609               case MoveNumberOne:
11610               case EndOfFile:
11611                 gn--;           /* count this game */
11612                 lastLoadGameStart = cm;
11613                 break;
11614               default:
11615                 /* impossible */
11616                 break;
11617             }
11618             break;
11619
11620           case PGNTag:
11621             switch (lastLoadGameStart) {
11622               case GNUChessGame:
11623               case PGNTag:
11624               case MoveNumberOne:
11625               case EndOfFile:
11626                 gn--;           /* count this game */
11627                 lastLoadGameStart = cm;
11628                 break;
11629               case XBoardGame:
11630                 lastLoadGameStart = cm; /* game counted already */
11631                 break;
11632               default:
11633                 /* impossible */
11634                 break;
11635             }
11636             if (gn > 0) {
11637                 do {
11638                     yyboardindex = forwardMostMove;
11639                     cm = (ChessMove) Myylex();
11640                 } while (cm == PGNTag || cm == Comment);
11641             }
11642             break;
11643
11644           case WhiteWins:
11645           case BlackWins:
11646           case GameIsDrawn:
11647             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11648                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11649                     != CMAIL_OLD_RESULT) {
11650                     nCmailResults ++ ;
11651                     cmailResult[  CMAIL_MAX_GAMES
11652                                 - gn - 1] = CMAIL_OLD_RESULT;
11653                 }
11654             }
11655             break;
11656
11657           case NormalMove:
11658             /* Only a NormalMove can be at the start of a game
11659              * without a position diagram. */
11660             if (lastLoadGameStart == EndOfFile ) {
11661               gn--;
11662               lastLoadGameStart = MoveNumberOne;
11663             }
11664             break;
11665
11666           default:
11667             break;
11668         }
11669     }
11670
11671     if (appData.debugMode)
11672       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11673
11674     if (cm == XBoardGame) {
11675         /* Skip any header junk before position diagram and/or move 1 */
11676         for (;;) {
11677             yyboardindex = forwardMostMove;
11678             cm = (ChessMove) Myylex();
11679
11680             if (cm == EndOfFile ||
11681                 cm == GNUChessGame || cm == XBoardGame) {
11682                 /* Empty game; pretend end-of-file and handle later */
11683                 cm = EndOfFile;
11684                 break;
11685             }
11686
11687             if (cm == MoveNumberOne || cm == PositionDiagram ||
11688                 cm == PGNTag || cm == Comment)
11689               break;
11690         }
11691     } else if (cm == GNUChessGame) {
11692         if (gameInfo.event != NULL) {
11693             free(gameInfo.event);
11694         }
11695         gameInfo.event = StrSave(yy_text);
11696     }
11697
11698     startedFromSetupPosition = FALSE;
11699     while (cm == PGNTag) {
11700         if (appData.debugMode)
11701           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11702         err = ParsePGNTag(yy_text, &gameInfo);
11703         if (!err) numPGNTags++;
11704
11705         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11706         if(gameInfo.variant != oldVariant) {
11707             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11708             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11709             InitPosition(TRUE);
11710             oldVariant = gameInfo.variant;
11711             if (appData.debugMode)
11712               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11713         }
11714
11715
11716         if (gameInfo.fen != NULL) {
11717           Board initial_position;
11718           startedFromSetupPosition = TRUE;
11719           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11720             Reset(TRUE, TRUE);
11721             DisplayError(_("Bad FEN position in file"), 0);
11722             return FALSE;
11723           }
11724           CopyBoard(boards[0], initial_position);
11725           if (blackPlaysFirst) {
11726             currentMove = forwardMostMove = backwardMostMove = 1;
11727             CopyBoard(boards[1], initial_position);
11728             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11729             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11730             timeRemaining[0][1] = whiteTimeRemaining;
11731             timeRemaining[1][1] = blackTimeRemaining;
11732             if (commentList[0] != NULL) {
11733               commentList[1] = commentList[0];
11734               commentList[0] = NULL;
11735             }
11736           } else {
11737             currentMove = forwardMostMove = backwardMostMove = 0;
11738           }
11739           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11740           {   int i;
11741               initialRulePlies = FENrulePlies;
11742               for( i=0; i< nrCastlingRights; i++ )
11743                   initialRights[i] = initial_position[CASTLING][i];
11744           }
11745           yyboardindex = forwardMostMove;
11746           free(gameInfo.fen);
11747           gameInfo.fen = NULL;
11748         }
11749
11750         yyboardindex = forwardMostMove;
11751         cm = (ChessMove) Myylex();
11752
11753         /* Handle comments interspersed among the tags */
11754         while (cm == Comment) {
11755             char *p;
11756             if (appData.debugMode)
11757               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11758             p = yy_text;
11759             AppendComment(currentMove, p, FALSE);
11760             yyboardindex = forwardMostMove;
11761             cm = (ChessMove) Myylex();
11762         }
11763     }
11764
11765     /* don't rely on existence of Event tag since if game was
11766      * pasted from clipboard the Event tag may not exist
11767      */
11768     if (numPGNTags > 0){
11769         char *tags;
11770         if (gameInfo.variant == VariantNormal) {
11771           VariantClass v = StringToVariant(gameInfo.event);
11772           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11773           if(v < VariantShogi) gameInfo.variant = v;
11774         }
11775         if (!matchMode) {
11776           if( appData.autoDisplayTags ) {
11777             tags = PGNTags(&gameInfo);
11778             TagsPopUp(tags, CmailMsg());
11779             free(tags);
11780           }
11781         }
11782     } else {
11783         /* Make something up, but don't display it now */
11784         SetGameInfo();
11785         TagsPopDown();
11786     }
11787
11788     if (cm == PositionDiagram) {
11789         int i, j;
11790         char *p;
11791         Board initial_position;
11792
11793         if (appData.debugMode)
11794           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11795
11796         if (!startedFromSetupPosition) {
11797             p = yy_text;
11798             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11799               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11800                 switch (*p) {
11801                   case '{':
11802                   case '[':
11803                   case '-':
11804                   case ' ':
11805                   case '\t':
11806                   case '\n':
11807                   case '\r':
11808                     break;
11809                   default:
11810                     initial_position[i][j++] = CharToPiece(*p);
11811                     break;
11812                 }
11813             while (*p == ' ' || *p == '\t' ||
11814                    *p == '\n' || *p == '\r') p++;
11815
11816             if (strncmp(p, "black", strlen("black"))==0)
11817               blackPlaysFirst = TRUE;
11818             else
11819               blackPlaysFirst = FALSE;
11820             startedFromSetupPosition = TRUE;
11821
11822             CopyBoard(boards[0], initial_position);
11823             if (blackPlaysFirst) {
11824                 currentMove = forwardMostMove = backwardMostMove = 1;
11825                 CopyBoard(boards[1], initial_position);
11826                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11827                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11828                 timeRemaining[0][1] = whiteTimeRemaining;
11829                 timeRemaining[1][1] = blackTimeRemaining;
11830                 if (commentList[0] != NULL) {
11831                     commentList[1] = commentList[0];
11832                     commentList[0] = NULL;
11833                 }
11834             } else {
11835                 currentMove = forwardMostMove = backwardMostMove = 0;
11836             }
11837         }
11838         yyboardindex = forwardMostMove;
11839         cm = (ChessMove) Myylex();
11840     }
11841
11842     if (first.pr == NoProc) {
11843         StartChessProgram(&first);
11844     }
11845     InitChessProgram(&first, FALSE);
11846     SendToProgram("force\n", &first);
11847     if (startedFromSetupPosition) {
11848         SendBoard(&first, forwardMostMove);
11849     if (appData.debugMode) {
11850         fprintf(debugFP, "Load Game\n");
11851     }
11852         DisplayBothClocks();
11853     }
11854
11855     /* [HGM] server: flag to write setup moves in broadcast file as one */
11856     loadFlag = appData.suppressLoadMoves;
11857
11858     while (cm == Comment) {
11859         char *p;
11860         if (appData.debugMode)
11861           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11862         p = yy_text;
11863         AppendComment(currentMove, p, FALSE);
11864         yyboardindex = forwardMostMove;
11865         cm = (ChessMove) Myylex();
11866     }
11867
11868     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11869         cm == WhiteWins || cm == BlackWins ||
11870         cm == GameIsDrawn || cm == GameUnfinished) {
11871         DisplayMessage("", _("No moves in game"));
11872         if (cmailMsgLoaded) {
11873             if (appData.debugMode)
11874               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11875             ClearHighlights();
11876             flipView = FALSE;
11877         }
11878         DrawPosition(FALSE, boards[currentMove]);
11879         DisplayBothClocks();
11880         gameMode = EditGame;
11881         ModeHighlight();
11882         gameFileFP = NULL;
11883         cmailOldMove = 0;
11884         return TRUE;
11885     }
11886
11887     // [HGM] PV info: routine tests if comment empty
11888     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11889         DisplayComment(currentMove - 1, commentList[currentMove]);
11890     }
11891     if (!matchMode && appData.timeDelay != 0)
11892       DrawPosition(FALSE, boards[currentMove]);
11893
11894     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11895       programStats.ok_to_send = 1;
11896     }
11897
11898     /* if the first token after the PGN tags is a move
11899      * and not move number 1, retrieve it from the parser
11900      */
11901     if (cm != MoveNumberOne)
11902         LoadGameOneMove(cm);
11903
11904     /* load the remaining moves from the file */
11905     while (LoadGameOneMove(EndOfFile)) {
11906       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11907       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11908     }
11909
11910     /* rewind to the start of the game */
11911     currentMove = backwardMostMove;
11912
11913     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11914
11915     if (oldGameMode == AnalyzeFile ||
11916         oldGameMode == AnalyzeMode) {
11917       AnalyzeFileEvent();
11918     }
11919
11920     if (!matchMode && pos >= 0) {
11921         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11922     } else
11923     if (matchMode || appData.timeDelay == 0) {
11924       ToEndEvent();
11925     } else if (appData.timeDelay > 0) {
11926       AutoPlayGameLoop();
11927     }
11928
11929     if (appData.debugMode)
11930         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11931
11932     loadFlag = 0; /* [HGM] true game starts */
11933     return TRUE;
11934 }
11935
11936 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11937 int
11938 ReloadPosition (int offset)
11939 {
11940     int positionNumber = lastLoadPositionNumber + offset;
11941     if (lastLoadPositionFP == NULL) {
11942         DisplayError(_("No position has been loaded yet"), 0);
11943         return FALSE;
11944     }
11945     if (positionNumber <= 0) {
11946         DisplayError(_("Can't back up any further"), 0);
11947         return FALSE;
11948     }
11949     return LoadPosition(lastLoadPositionFP, positionNumber,
11950                         lastLoadPositionTitle);
11951 }
11952
11953 /* Load the nth position from the given file */
11954 int
11955 LoadPositionFromFile (char *filename, int n, char *title)
11956 {
11957     FILE *f;
11958     char buf[MSG_SIZ];
11959
11960     if (strcmp(filename, "-") == 0) {
11961         return LoadPosition(stdin, n, "stdin");
11962     } else {
11963         f = fopen(filename, "rb");
11964         if (f == NULL) {
11965             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11966             DisplayError(buf, errno);
11967             return FALSE;
11968         } else {
11969             return LoadPosition(f, n, title);
11970         }
11971     }
11972 }
11973
11974 /* Load the nth position from the given open file, and close it */
11975 int
11976 LoadPosition (FILE *f, int positionNumber, char *title)
11977 {
11978     char *p, line[MSG_SIZ];
11979     Board initial_position;
11980     int i, j, fenMode, pn;
11981
11982     if (gameMode == Training )
11983         SetTrainingModeOff();
11984
11985     if (gameMode != BeginningOfGame) {
11986         Reset(FALSE, TRUE);
11987     }
11988     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11989         fclose(lastLoadPositionFP);
11990     }
11991     if (positionNumber == 0) positionNumber = 1;
11992     lastLoadPositionFP = f;
11993     lastLoadPositionNumber = positionNumber;
11994     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11995     if (first.pr == NoProc && !appData.noChessProgram) {
11996       StartChessProgram(&first);
11997       InitChessProgram(&first, FALSE);
11998     }
11999     pn = positionNumber;
12000     if (positionNumber < 0) {
12001         /* Negative position number means to seek to that byte offset */
12002         if (fseek(f, -positionNumber, 0) == -1) {
12003             DisplayError(_("Can't seek on position file"), 0);
12004             return FALSE;
12005         };
12006         pn = 1;
12007     } else {
12008         if (fseek(f, 0, 0) == -1) {
12009             if (f == lastLoadPositionFP ?
12010                 positionNumber == lastLoadPositionNumber + 1 :
12011                 positionNumber == 1) {
12012                 pn = 1;
12013             } else {
12014                 DisplayError(_("Can't seek on position file"), 0);
12015                 return FALSE;
12016             }
12017         }
12018     }
12019     /* See if this file is FEN or old-style xboard */
12020     if (fgets(line, MSG_SIZ, f) == NULL) {
12021         DisplayError(_("Position not found in file"), 0);
12022         return FALSE;
12023     }
12024     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12025     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12026
12027     if (pn >= 2) {
12028         if (fenMode || line[0] == '#') pn--;
12029         while (pn > 0) {
12030             /* skip positions before number pn */
12031             if (fgets(line, MSG_SIZ, f) == NULL) {
12032                 Reset(TRUE, TRUE);
12033                 DisplayError(_("Position not found in file"), 0);
12034                 return FALSE;
12035             }
12036             if (fenMode || line[0] == '#') pn--;
12037         }
12038     }
12039
12040     if (fenMode) {
12041         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12042             DisplayError(_("Bad FEN position in file"), 0);
12043             return FALSE;
12044         }
12045     } else {
12046         (void) fgets(line, MSG_SIZ, f);
12047         (void) fgets(line, MSG_SIZ, f);
12048
12049         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12050             (void) fgets(line, MSG_SIZ, f);
12051             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12052                 if (*p == ' ')
12053                   continue;
12054                 initial_position[i][j++] = CharToPiece(*p);
12055             }
12056         }
12057
12058         blackPlaysFirst = FALSE;
12059         if (!feof(f)) {
12060             (void) fgets(line, MSG_SIZ, f);
12061             if (strncmp(line, "black", strlen("black"))==0)
12062               blackPlaysFirst = TRUE;
12063         }
12064     }
12065     startedFromSetupPosition = TRUE;
12066
12067     CopyBoard(boards[0], initial_position);
12068     if (blackPlaysFirst) {
12069         currentMove = forwardMostMove = backwardMostMove = 1;
12070         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12071         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12072         CopyBoard(boards[1], initial_position);
12073         DisplayMessage("", _("Black to play"));
12074     } else {
12075         currentMove = forwardMostMove = backwardMostMove = 0;
12076         DisplayMessage("", _("White to play"));
12077     }
12078     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12079     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12080         SendToProgram("force\n", &first);
12081         SendBoard(&first, forwardMostMove);
12082     }
12083     if (appData.debugMode) {
12084 int i, j;
12085   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12086   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12087         fprintf(debugFP, "Load Position\n");
12088     }
12089
12090     if (positionNumber > 1) {
12091       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12092         DisplayTitle(line);
12093     } else {
12094         DisplayTitle(title);
12095     }
12096     gameMode = EditGame;
12097     ModeHighlight();
12098     ResetClocks();
12099     timeRemaining[0][1] = whiteTimeRemaining;
12100     timeRemaining[1][1] = blackTimeRemaining;
12101     DrawPosition(FALSE, boards[currentMove]);
12102
12103     return TRUE;
12104 }
12105
12106
12107 void
12108 CopyPlayerNameIntoFileName (char **dest, char *src)
12109 {
12110     while (*src != NULLCHAR && *src != ',') {
12111         if (*src == ' ') {
12112             *(*dest)++ = '_';
12113             src++;
12114         } else {
12115             *(*dest)++ = *src++;
12116         }
12117     }
12118 }
12119
12120 char *
12121 DefaultFileName (char *ext)
12122 {
12123     static char def[MSG_SIZ];
12124     char *p;
12125
12126     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12127         p = def;
12128         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12129         *p++ = '-';
12130         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12131         *p++ = '.';
12132         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12133     } else {
12134         def[0] = NULLCHAR;
12135     }
12136     return def;
12137 }
12138
12139 /* Save the current game to the given file */
12140 int
12141 SaveGameToFile (char *filename, int append)
12142 {
12143     FILE *f;
12144     char buf[MSG_SIZ];
12145     int result, i, t,tot=0;
12146
12147     if (strcmp(filename, "-") == 0) {
12148         return SaveGame(stdout, 0, NULL);
12149     } else {
12150         for(i=0; i<10; i++) { // upto 10 tries
12151              f = fopen(filename, append ? "a" : "w");
12152              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12153              if(f || errno != 13) break;
12154              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12155              tot += t;
12156         }
12157         if (f == NULL) {
12158             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12159             DisplayError(buf, errno);
12160             return FALSE;
12161         } else {
12162             safeStrCpy(buf, lastMsg, MSG_SIZ);
12163             DisplayMessage(_("Waiting for access to save file"), "");
12164             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12165             DisplayMessage(_("Saving game"), "");
12166             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12167             result = SaveGame(f, 0, NULL);
12168             DisplayMessage(buf, "");
12169             return result;
12170         }
12171     }
12172 }
12173
12174 char *
12175 SavePart (char *str)
12176 {
12177     static char buf[MSG_SIZ];
12178     char *p;
12179
12180     p = strchr(str, ' ');
12181     if (p == NULL) return str;
12182     strncpy(buf, str, p - str);
12183     buf[p - str] = NULLCHAR;
12184     return buf;
12185 }
12186
12187 #define PGN_MAX_LINE 75
12188
12189 #define PGN_SIDE_WHITE  0
12190 #define PGN_SIDE_BLACK  1
12191
12192 static int
12193 FindFirstMoveOutOfBook (int side)
12194 {
12195     int result = -1;
12196
12197     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12198         int index = backwardMostMove;
12199         int has_book_hit = 0;
12200
12201         if( (index % 2) != side ) {
12202             index++;
12203         }
12204
12205         while( index < forwardMostMove ) {
12206             /* Check to see if engine is in book */
12207             int depth = pvInfoList[index].depth;
12208             int score = pvInfoList[index].score;
12209             int in_book = 0;
12210
12211             if( depth <= 2 ) {
12212                 in_book = 1;
12213             }
12214             else if( score == 0 && depth == 63 ) {
12215                 in_book = 1; /* Zappa */
12216             }
12217             else if( score == 2 && depth == 99 ) {
12218                 in_book = 1; /* Abrok */
12219             }
12220
12221             has_book_hit += in_book;
12222
12223             if( ! in_book ) {
12224                 result = index;
12225
12226                 break;
12227             }
12228
12229             index += 2;
12230         }
12231     }
12232
12233     return result;
12234 }
12235
12236 void
12237 GetOutOfBookInfo (char * buf)
12238 {
12239     int oob[2];
12240     int i;
12241     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12242
12243     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12244     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12245
12246     *buf = '\0';
12247
12248     if( oob[0] >= 0 || oob[1] >= 0 ) {
12249         for( i=0; i<2; i++ ) {
12250             int idx = oob[i];
12251
12252             if( idx >= 0 ) {
12253                 if( i > 0 && oob[0] >= 0 ) {
12254                     strcat( buf, "   " );
12255                 }
12256
12257                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12258                 sprintf( buf+strlen(buf), "%s%.2f",
12259                     pvInfoList[idx].score >= 0 ? "+" : "",
12260                     pvInfoList[idx].score / 100.0 );
12261             }
12262         }
12263     }
12264 }
12265
12266 /* Save game in PGN style and close the file */
12267 int
12268 SaveGamePGN (FILE *f)
12269 {
12270     int i, offset, linelen, newblock;
12271     time_t tm;
12272 //    char *movetext;
12273     char numtext[32];
12274     int movelen, numlen, blank;
12275     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12276
12277     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12278
12279     tm = time((time_t *) NULL);
12280
12281     PrintPGNTags(f, &gameInfo);
12282
12283     if (backwardMostMove > 0 || startedFromSetupPosition) {
12284         char *fen = PositionToFEN(backwardMostMove, NULL);
12285         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12286         fprintf(f, "\n{--------------\n");
12287         PrintPosition(f, backwardMostMove);
12288         fprintf(f, "--------------}\n");
12289         free(fen);
12290     }
12291     else {
12292         /* [AS] Out of book annotation */
12293         if( appData.saveOutOfBookInfo ) {
12294             char buf[64];
12295
12296             GetOutOfBookInfo( buf );
12297
12298             if( buf[0] != '\0' ) {
12299                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12300             }
12301         }
12302
12303         fprintf(f, "\n");
12304     }
12305
12306     i = backwardMostMove;
12307     linelen = 0;
12308     newblock = TRUE;
12309
12310     while (i < forwardMostMove) {
12311         /* Print comments preceding this move */
12312         if (commentList[i] != NULL) {
12313             if (linelen > 0) fprintf(f, "\n");
12314             fprintf(f, "%s", commentList[i]);
12315             linelen = 0;
12316             newblock = TRUE;
12317         }
12318
12319         /* Format move number */
12320         if ((i % 2) == 0)
12321           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12322         else
12323           if (newblock)
12324             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12325           else
12326             numtext[0] = NULLCHAR;
12327
12328         numlen = strlen(numtext);
12329         newblock = FALSE;
12330
12331         /* Print move number */
12332         blank = linelen > 0 && numlen > 0;
12333         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12334             fprintf(f, "\n");
12335             linelen = 0;
12336             blank = 0;
12337         }
12338         if (blank) {
12339             fprintf(f, " ");
12340             linelen++;
12341         }
12342         fprintf(f, "%s", numtext);
12343         linelen += numlen;
12344
12345         /* Get move */
12346         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12347         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12348
12349         /* Print move */
12350         blank = linelen > 0 && movelen > 0;
12351         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12352             fprintf(f, "\n");
12353             linelen = 0;
12354             blank = 0;
12355         }
12356         if (blank) {
12357             fprintf(f, " ");
12358             linelen++;
12359         }
12360         fprintf(f, "%s", move_buffer);
12361         linelen += movelen;
12362
12363         /* [AS] Add PV info if present */
12364         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12365             /* [HGM] add time */
12366             char buf[MSG_SIZ]; int seconds;
12367
12368             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12369
12370             if( seconds <= 0)
12371               buf[0] = 0;
12372             else
12373               if( seconds < 30 )
12374                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12375               else
12376                 {
12377                   seconds = (seconds + 4)/10; // round to full seconds
12378                   if( seconds < 60 )
12379                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12380                   else
12381                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12382                 }
12383
12384             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12385                       pvInfoList[i].score >= 0 ? "+" : "",
12386                       pvInfoList[i].score / 100.0,
12387                       pvInfoList[i].depth,
12388                       buf );
12389
12390             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12391
12392             /* Print score/depth */
12393             blank = linelen > 0 && movelen > 0;
12394             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12395                 fprintf(f, "\n");
12396                 linelen = 0;
12397                 blank = 0;
12398             }
12399             if (blank) {
12400                 fprintf(f, " ");
12401                 linelen++;
12402             }
12403             fprintf(f, "%s", move_buffer);
12404             linelen += movelen;
12405         }
12406
12407         i++;
12408     }
12409
12410     /* Start a new line */
12411     if (linelen > 0) fprintf(f, "\n");
12412
12413     /* Print comments after last move */
12414     if (commentList[i] != NULL) {
12415         fprintf(f, "%s\n", commentList[i]);
12416     }
12417
12418     /* Print result */
12419     if (gameInfo.resultDetails != NULL &&
12420         gameInfo.resultDetails[0] != NULLCHAR) {
12421         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12422                 PGNResult(gameInfo.result));
12423     } else {
12424         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12425     }
12426
12427     fclose(f);
12428     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12429     return TRUE;
12430 }
12431
12432 /* Save game in old style and close the file */
12433 int
12434 SaveGameOldStyle (FILE *f)
12435 {
12436     int i, offset;
12437     time_t tm;
12438
12439     tm = time((time_t *) NULL);
12440
12441     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12442     PrintOpponents(f);
12443
12444     if (backwardMostMove > 0 || startedFromSetupPosition) {
12445         fprintf(f, "\n[--------------\n");
12446         PrintPosition(f, backwardMostMove);
12447         fprintf(f, "--------------]\n");
12448     } else {
12449         fprintf(f, "\n");
12450     }
12451
12452     i = backwardMostMove;
12453     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12454
12455     while (i < forwardMostMove) {
12456         if (commentList[i] != NULL) {
12457             fprintf(f, "[%s]\n", commentList[i]);
12458         }
12459
12460         if ((i % 2) == 1) {
12461             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12462             i++;
12463         } else {
12464             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12465             i++;
12466             if (commentList[i] != NULL) {
12467                 fprintf(f, "\n");
12468                 continue;
12469             }
12470             if (i >= forwardMostMove) {
12471                 fprintf(f, "\n");
12472                 break;
12473             }
12474             fprintf(f, "%s\n", parseList[i]);
12475             i++;
12476         }
12477     }
12478
12479     if (commentList[i] != NULL) {
12480         fprintf(f, "[%s]\n", commentList[i]);
12481     }
12482
12483     /* This isn't really the old style, but it's close enough */
12484     if (gameInfo.resultDetails != NULL &&
12485         gameInfo.resultDetails[0] != NULLCHAR) {
12486         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12487                 gameInfo.resultDetails);
12488     } else {
12489         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12490     }
12491
12492     fclose(f);
12493     return TRUE;
12494 }
12495
12496 /* Save the current game to open file f and close the file */
12497 int
12498 SaveGame (FILE *f, int dummy, char *dummy2)
12499 {
12500     if (gameMode == EditPosition) EditPositionDone(TRUE);
12501     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12502     if (appData.oldSaveStyle)
12503       return SaveGameOldStyle(f);
12504     else
12505       return SaveGamePGN(f);
12506 }
12507
12508 /* Save the current position to the given file */
12509 int
12510 SavePositionToFile (char *filename)
12511 {
12512     FILE *f;
12513     char buf[MSG_SIZ];
12514
12515     if (strcmp(filename, "-") == 0) {
12516         return SavePosition(stdout, 0, NULL);
12517     } else {
12518         f = fopen(filename, "a");
12519         if (f == NULL) {
12520             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12521             DisplayError(buf, errno);
12522             return FALSE;
12523         } else {
12524             safeStrCpy(buf, lastMsg, MSG_SIZ);
12525             DisplayMessage(_("Waiting for access to save file"), "");
12526             flock(fileno(f), LOCK_EX); // [HGM] lock
12527             DisplayMessage(_("Saving position"), "");
12528             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12529             SavePosition(f, 0, NULL);
12530             DisplayMessage(buf, "");
12531             return TRUE;
12532         }
12533     }
12534 }
12535
12536 /* Save the current position to the given open file and close the file */
12537 int
12538 SavePosition (FILE *f, int dummy, char *dummy2)
12539 {
12540     time_t tm;
12541     char *fen;
12542
12543     if (gameMode == EditPosition) EditPositionDone(TRUE);
12544     if (appData.oldSaveStyle) {
12545         tm = time((time_t *) NULL);
12546
12547         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12548         PrintOpponents(f);
12549         fprintf(f, "[--------------\n");
12550         PrintPosition(f, currentMove);
12551         fprintf(f, "--------------]\n");
12552     } else {
12553         fen = PositionToFEN(currentMove, NULL);
12554         fprintf(f, "%s\n", fen);
12555         free(fen);
12556     }
12557     fclose(f);
12558     return TRUE;
12559 }
12560
12561 void
12562 ReloadCmailMsgEvent (int unregister)
12563 {
12564 #if !WIN32
12565     static char *inFilename = NULL;
12566     static char *outFilename;
12567     int i;
12568     struct stat inbuf, outbuf;
12569     int status;
12570
12571     /* Any registered moves are unregistered if unregister is set, */
12572     /* i.e. invoked by the signal handler */
12573     if (unregister) {
12574         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12575             cmailMoveRegistered[i] = FALSE;
12576             if (cmailCommentList[i] != NULL) {
12577                 free(cmailCommentList[i]);
12578                 cmailCommentList[i] = NULL;
12579             }
12580         }
12581         nCmailMovesRegistered = 0;
12582     }
12583
12584     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12585         cmailResult[i] = CMAIL_NOT_RESULT;
12586     }
12587     nCmailResults = 0;
12588
12589     if (inFilename == NULL) {
12590         /* Because the filenames are static they only get malloced once  */
12591         /* and they never get freed                                      */
12592         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12593         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12594
12595         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12596         sprintf(outFilename, "%s.out", appData.cmailGameName);
12597     }
12598
12599     status = stat(outFilename, &outbuf);
12600     if (status < 0) {
12601         cmailMailedMove = FALSE;
12602     } else {
12603         status = stat(inFilename, &inbuf);
12604         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12605     }
12606
12607     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12608        counts the games, notes how each one terminated, etc.
12609
12610        It would be nice to remove this kludge and instead gather all
12611        the information while building the game list.  (And to keep it
12612        in the game list nodes instead of having a bunch of fixed-size
12613        parallel arrays.)  Note this will require getting each game's
12614        termination from the PGN tags, as the game list builder does
12615        not process the game moves.  --mann
12616        */
12617     cmailMsgLoaded = TRUE;
12618     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12619
12620     /* Load first game in the file or popup game menu */
12621     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12622
12623 #endif /* !WIN32 */
12624     return;
12625 }
12626
12627 int
12628 RegisterMove ()
12629 {
12630     FILE *f;
12631     char string[MSG_SIZ];
12632
12633     if (   cmailMailedMove
12634         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12635         return TRUE;            /* Allow free viewing  */
12636     }
12637
12638     /* Unregister move to ensure that we don't leave RegisterMove        */
12639     /* with the move registered when the conditions for registering no   */
12640     /* longer hold                                                       */
12641     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12642         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12643         nCmailMovesRegistered --;
12644
12645         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12646           {
12647               free(cmailCommentList[lastLoadGameNumber - 1]);
12648               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12649           }
12650     }
12651
12652     if (cmailOldMove == -1) {
12653         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12654         return FALSE;
12655     }
12656
12657     if (currentMove > cmailOldMove + 1) {
12658         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12659         return FALSE;
12660     }
12661
12662     if (currentMove < cmailOldMove) {
12663         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12664         return FALSE;
12665     }
12666
12667     if (forwardMostMove > currentMove) {
12668         /* Silently truncate extra moves */
12669         TruncateGame();
12670     }
12671
12672     if (   (currentMove == cmailOldMove + 1)
12673         || (   (currentMove == cmailOldMove)
12674             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12675                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12676         if (gameInfo.result != GameUnfinished) {
12677             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12678         }
12679
12680         if (commentList[currentMove] != NULL) {
12681             cmailCommentList[lastLoadGameNumber - 1]
12682               = StrSave(commentList[currentMove]);
12683         }
12684         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12685
12686         if (appData.debugMode)
12687           fprintf(debugFP, "Saving %s for game %d\n",
12688                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12689
12690         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12691
12692         f = fopen(string, "w");
12693         if (appData.oldSaveStyle) {
12694             SaveGameOldStyle(f); /* also closes the file */
12695
12696             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12697             f = fopen(string, "w");
12698             SavePosition(f, 0, NULL); /* also closes the file */
12699         } else {
12700             fprintf(f, "{--------------\n");
12701             PrintPosition(f, currentMove);
12702             fprintf(f, "--------------}\n\n");
12703
12704             SaveGame(f, 0, NULL); /* also closes the file*/
12705         }
12706
12707         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12708         nCmailMovesRegistered ++;
12709     } else if (nCmailGames == 1) {
12710         DisplayError(_("You have not made a move yet"), 0);
12711         return FALSE;
12712     }
12713
12714     return TRUE;
12715 }
12716
12717 void
12718 MailMoveEvent ()
12719 {
12720 #if !WIN32
12721     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12722     FILE *commandOutput;
12723     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12724     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12725     int nBuffers;
12726     int i;
12727     int archived;
12728     char *arcDir;
12729
12730     if (! cmailMsgLoaded) {
12731         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12732         return;
12733     }
12734
12735     if (nCmailGames == nCmailResults) {
12736         DisplayError(_("No unfinished games"), 0);
12737         return;
12738     }
12739
12740 #if CMAIL_PROHIBIT_REMAIL
12741     if (cmailMailedMove) {
12742       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);
12743         DisplayError(msg, 0);
12744         return;
12745     }
12746 #endif
12747
12748     if (! (cmailMailedMove || RegisterMove())) return;
12749
12750     if (   cmailMailedMove
12751         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12752       snprintf(string, MSG_SIZ, partCommandString,
12753                appData.debugMode ? " -v" : "", appData.cmailGameName);
12754         commandOutput = popen(string, "r");
12755
12756         if (commandOutput == NULL) {
12757             DisplayError(_("Failed to invoke cmail"), 0);
12758         } else {
12759             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12760                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12761             }
12762             if (nBuffers > 1) {
12763                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12764                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12765                 nBytes = MSG_SIZ - 1;
12766             } else {
12767                 (void) memcpy(msg, buffer, nBytes);
12768             }
12769             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12770
12771             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12772                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12773
12774                 archived = TRUE;
12775                 for (i = 0; i < nCmailGames; i ++) {
12776                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12777                         archived = FALSE;
12778                     }
12779                 }
12780                 if (   archived
12781                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12782                         != NULL)) {
12783                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12784                            arcDir,
12785                            appData.cmailGameName,
12786                            gameInfo.date);
12787                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12788                     cmailMsgLoaded = FALSE;
12789                 }
12790             }
12791
12792             DisplayInformation(msg);
12793             pclose(commandOutput);
12794         }
12795     } else {
12796         if ((*cmailMsg) != '\0') {
12797             DisplayInformation(cmailMsg);
12798         }
12799     }
12800
12801     return;
12802 #endif /* !WIN32 */
12803 }
12804
12805 char *
12806 CmailMsg ()
12807 {
12808 #if WIN32
12809     return NULL;
12810 #else
12811     int  prependComma = 0;
12812     char number[5];
12813     char string[MSG_SIZ];       /* Space for game-list */
12814     int  i;
12815
12816     if (!cmailMsgLoaded) return "";
12817
12818     if (cmailMailedMove) {
12819       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12820     } else {
12821         /* Create a list of games left */
12822       snprintf(string, MSG_SIZ, "[");
12823         for (i = 0; i < nCmailGames; i ++) {
12824             if (! (   cmailMoveRegistered[i]
12825                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12826                 if (prependComma) {
12827                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12828                 } else {
12829                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12830                     prependComma = 1;
12831                 }
12832
12833                 strcat(string, number);
12834             }
12835         }
12836         strcat(string, "]");
12837
12838         if (nCmailMovesRegistered + nCmailResults == 0) {
12839             switch (nCmailGames) {
12840               case 1:
12841                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12842                 break;
12843
12844               case 2:
12845                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12846                 break;
12847
12848               default:
12849                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12850                          nCmailGames);
12851                 break;
12852             }
12853         } else {
12854             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12855               case 1:
12856                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12857                          string);
12858                 break;
12859
12860               case 0:
12861                 if (nCmailResults == nCmailGames) {
12862                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12863                 } else {
12864                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12865                 }
12866                 break;
12867
12868               default:
12869                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12870                          string);
12871             }
12872         }
12873     }
12874     return cmailMsg;
12875 #endif /* WIN32 */
12876 }
12877
12878 void
12879 ResetGameEvent ()
12880 {
12881     if (gameMode == Training)
12882       SetTrainingModeOff();
12883
12884     Reset(TRUE, TRUE);
12885     cmailMsgLoaded = FALSE;
12886     if (appData.icsActive) {
12887       SendToICS(ics_prefix);
12888       SendToICS("refresh\n");
12889     }
12890 }
12891
12892 void
12893 ExitEvent (int status)
12894 {
12895     exiting++;
12896     if (exiting > 2) {
12897       /* Give up on clean exit */
12898       exit(status);
12899     }
12900     if (exiting > 1) {
12901       /* Keep trying for clean exit */
12902       return;
12903     }
12904
12905     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12906
12907     if (telnetISR != NULL) {
12908       RemoveInputSource(telnetISR);
12909     }
12910     if (icsPR != NoProc) {
12911       DestroyChildProcess(icsPR, TRUE);
12912     }
12913
12914     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12915     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12916
12917     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12918     /* make sure this other one finishes before killing it!                  */
12919     if(endingGame) { int count = 0;
12920         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12921         while(endingGame && count++ < 10) DoSleep(1);
12922         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12923     }
12924
12925     /* Kill off chess programs */
12926     if (first.pr != NoProc) {
12927         ExitAnalyzeMode();
12928
12929         DoSleep( appData.delayBeforeQuit );
12930         SendToProgram("quit\n", &first);
12931         DoSleep( appData.delayAfterQuit );
12932         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12933     }
12934     if (second.pr != NoProc) {
12935         DoSleep( appData.delayBeforeQuit );
12936         SendToProgram("quit\n", &second);
12937         DoSleep( appData.delayAfterQuit );
12938         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12939     }
12940     if (first.isr != NULL) {
12941         RemoveInputSource(first.isr);
12942     }
12943     if (second.isr != NULL) {
12944         RemoveInputSource(second.isr);
12945     }
12946
12947     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12948     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12949
12950     ShutDownFrontEnd();
12951     exit(status);
12952 }
12953
12954 void
12955 PauseEvent ()
12956 {
12957     if (appData.debugMode)
12958         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12959     if (pausing) {
12960         pausing = FALSE;
12961         ModeHighlight();
12962         if (gameMode == MachinePlaysWhite ||
12963             gameMode == MachinePlaysBlack) {
12964             StartClocks();
12965         } else {
12966             DisplayBothClocks();
12967         }
12968         if (gameMode == PlayFromGameFile) {
12969             if (appData.timeDelay >= 0)
12970                 AutoPlayGameLoop();
12971         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12972             Reset(FALSE, TRUE);
12973             SendToICS(ics_prefix);
12974             SendToICS("refresh\n");
12975         } else if (currentMove < forwardMostMove) {
12976             ForwardInner(forwardMostMove);
12977         }
12978         pauseExamInvalid = FALSE;
12979     } else {
12980         switch (gameMode) {
12981           default:
12982             return;
12983           case IcsExamining:
12984             pauseExamForwardMostMove = forwardMostMove;
12985             pauseExamInvalid = FALSE;
12986             /* fall through */
12987           case IcsObserving:
12988           case IcsPlayingWhite:
12989           case IcsPlayingBlack:
12990             pausing = TRUE;
12991             ModeHighlight();
12992             return;
12993           case PlayFromGameFile:
12994             (void) StopLoadGameTimer();
12995             pausing = TRUE;
12996             ModeHighlight();
12997             break;
12998           case BeginningOfGame:
12999             if (appData.icsActive) return;
13000             /* else fall through */
13001           case MachinePlaysWhite:
13002           case MachinePlaysBlack:
13003           case TwoMachinesPlay:
13004             if (forwardMostMove == 0)
13005               return;           /* don't pause if no one has moved */
13006             if ((gameMode == MachinePlaysWhite &&
13007                  !WhiteOnMove(forwardMostMove)) ||
13008                 (gameMode == MachinePlaysBlack &&
13009                  WhiteOnMove(forwardMostMove))) {
13010                 StopClocks();
13011             }
13012             pausing = TRUE;
13013             ModeHighlight();
13014             break;
13015         }
13016     }
13017 }
13018
13019 void
13020 EditCommentEvent ()
13021 {
13022     char title[MSG_SIZ];
13023
13024     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13025       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13026     } else {
13027       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13028                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13029                parseList[currentMove - 1]);
13030     }
13031
13032     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13033 }
13034
13035
13036 void
13037 EditTagsEvent ()
13038 {
13039     char *tags = PGNTags(&gameInfo);
13040     bookUp = FALSE;
13041     EditTagsPopUp(tags, NULL);
13042     free(tags);
13043 }
13044
13045 void
13046 AnalyzeModeEvent ()
13047 {
13048     if (appData.noChessProgram || gameMode == AnalyzeMode)
13049       return;
13050
13051     if (gameMode != AnalyzeFile) {
13052         if (!appData.icsEngineAnalyze) {
13053                EditGameEvent();
13054                if (gameMode != EditGame) return;
13055         }
13056         ResurrectChessProgram();
13057         SendToProgram("analyze\n", &first);
13058         first.analyzing = TRUE;
13059         /*first.maybeThinking = TRUE;*/
13060         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13061         EngineOutputPopUp();
13062     }
13063     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13064     pausing = FALSE;
13065     ModeHighlight();
13066     SetGameInfo();
13067
13068     StartAnalysisClock();
13069     GetTimeMark(&lastNodeCountTime);
13070     lastNodeCount = 0;
13071 }
13072
13073 void
13074 AnalyzeFileEvent ()
13075 {
13076     if (appData.noChessProgram || gameMode == AnalyzeFile)
13077       return;
13078
13079     if (gameMode != AnalyzeMode) {
13080         EditGameEvent();
13081         if (gameMode != EditGame) return;
13082         ResurrectChessProgram();
13083         SendToProgram("analyze\n", &first);
13084         first.analyzing = TRUE;
13085         /*first.maybeThinking = TRUE;*/
13086         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13087         EngineOutputPopUp();
13088     }
13089     gameMode = AnalyzeFile;
13090     pausing = FALSE;
13091     ModeHighlight();
13092     SetGameInfo();
13093
13094     StartAnalysisClock();
13095     GetTimeMark(&lastNodeCountTime);
13096     lastNodeCount = 0;
13097     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13098 }
13099
13100 void
13101 MachineWhiteEvent ()
13102 {
13103     char buf[MSG_SIZ];
13104     char *bookHit = NULL;
13105
13106     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13107       return;
13108
13109
13110     if (gameMode == PlayFromGameFile ||
13111         gameMode == TwoMachinesPlay  ||
13112         gameMode == Training         ||
13113         gameMode == AnalyzeMode      ||
13114         gameMode == EndOfGame)
13115         EditGameEvent();
13116
13117     if (gameMode == EditPosition)
13118         EditPositionDone(TRUE);
13119
13120     if (!WhiteOnMove(currentMove)) {
13121         DisplayError(_("It is not White's turn"), 0);
13122         return;
13123     }
13124
13125     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13126       ExitAnalyzeMode();
13127
13128     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13129         gameMode == AnalyzeFile)
13130         TruncateGame();
13131
13132     ResurrectChessProgram();    /* in case it isn't running */
13133     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13134         gameMode = MachinePlaysWhite;
13135         ResetClocks();
13136     } else
13137     gameMode = MachinePlaysWhite;
13138     pausing = FALSE;
13139     ModeHighlight();
13140     SetGameInfo();
13141     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13142     DisplayTitle(buf);
13143     if (first.sendName) {
13144       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13145       SendToProgram(buf, &first);
13146     }
13147     if (first.sendTime) {
13148       if (first.useColors) {
13149         SendToProgram("black\n", &first); /*gnu kludge*/
13150       }
13151       SendTimeRemaining(&first, TRUE);
13152     }
13153     if (first.useColors) {
13154       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13155     }
13156     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13157     SetMachineThinkingEnables();
13158     first.maybeThinking = TRUE;
13159     StartClocks();
13160     firstMove = FALSE;
13161
13162     if (appData.autoFlipView && !flipView) {
13163       flipView = !flipView;
13164       DrawPosition(FALSE, NULL);
13165       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13166     }
13167
13168     if(bookHit) { // [HGM] book: simulate book reply
13169         static char bookMove[MSG_SIZ]; // a bit generous?
13170
13171         programStats.nodes = programStats.depth = programStats.time =
13172         programStats.score = programStats.got_only_move = 0;
13173         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13174
13175         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13176         strcat(bookMove, bookHit);
13177         HandleMachineMove(bookMove, &first);
13178     }
13179 }
13180
13181 void
13182 MachineBlackEvent ()
13183 {
13184   char buf[MSG_SIZ];
13185   char *bookHit = NULL;
13186
13187     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13188         return;
13189
13190
13191     if (gameMode == PlayFromGameFile ||
13192         gameMode == TwoMachinesPlay  ||
13193         gameMode == Training         ||
13194         gameMode == AnalyzeMode      ||
13195         gameMode == EndOfGame)
13196         EditGameEvent();
13197
13198     if (gameMode == EditPosition)
13199         EditPositionDone(TRUE);
13200
13201     if (WhiteOnMove(currentMove)) {
13202         DisplayError(_("It is not Black's turn"), 0);
13203         return;
13204     }
13205
13206     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13207       ExitAnalyzeMode();
13208
13209     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13210         gameMode == AnalyzeFile)
13211         TruncateGame();
13212
13213     ResurrectChessProgram();    /* in case it isn't running */
13214     gameMode = MachinePlaysBlack;
13215     pausing = FALSE;
13216     ModeHighlight();
13217     SetGameInfo();
13218     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13219     DisplayTitle(buf);
13220     if (first.sendName) {
13221       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13222       SendToProgram(buf, &first);
13223     }
13224     if (first.sendTime) {
13225       if (first.useColors) {
13226         SendToProgram("white\n", &first); /*gnu kludge*/
13227       }
13228       SendTimeRemaining(&first, FALSE);
13229     }
13230     if (first.useColors) {
13231       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13232     }
13233     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13234     SetMachineThinkingEnables();
13235     first.maybeThinking = TRUE;
13236     StartClocks();
13237
13238     if (appData.autoFlipView && flipView) {
13239       flipView = !flipView;
13240       DrawPosition(FALSE, NULL);
13241       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13242     }
13243     if(bookHit) { // [HGM] book: simulate book reply
13244         static char bookMove[MSG_SIZ]; // a bit generous?
13245
13246         programStats.nodes = programStats.depth = programStats.time =
13247         programStats.score = programStats.got_only_move = 0;
13248         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13249
13250         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13251         strcat(bookMove, bookHit);
13252         HandleMachineMove(bookMove, &first);
13253     }
13254 }
13255
13256
13257 void
13258 DisplayTwoMachinesTitle ()
13259 {
13260     char buf[MSG_SIZ];
13261     if (appData.matchGames > 0) {
13262         if(appData.tourneyFile[0]) {
13263           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13264                    gameInfo.white, _("vs."), gameInfo.black,
13265                    nextGame+1, appData.matchGames+1,
13266                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13267         } else 
13268         if (first.twoMachinesColor[0] == 'w') {
13269           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13270                    gameInfo.white, _("vs."),  gameInfo.black,
13271                    first.matchWins, second.matchWins,
13272                    matchGame - 1 - (first.matchWins + second.matchWins));
13273         } else {
13274           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13275                    gameInfo.white, _("vs."), gameInfo.black,
13276                    second.matchWins, first.matchWins,
13277                    matchGame - 1 - (first.matchWins + second.matchWins));
13278         }
13279     } else {
13280       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13281     }
13282     DisplayTitle(buf);
13283 }
13284
13285 void
13286 SettingsMenuIfReady ()
13287 {
13288   if (second.lastPing != second.lastPong) {
13289     DisplayMessage("", _("Waiting for second chess program"));
13290     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13291     return;
13292   }
13293   ThawUI();
13294   DisplayMessage("", "");
13295   SettingsPopUp(&second);
13296 }
13297
13298 int
13299 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13300 {
13301     char buf[MSG_SIZ];
13302     if (cps->pr == NoProc) {
13303         StartChessProgram(cps);
13304         if (cps->protocolVersion == 1) {
13305           retry();
13306         } else {
13307           /* kludge: allow timeout for initial "feature" command */
13308           FreezeUI();
13309           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13310           DisplayMessage("", buf);
13311           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13312         }
13313         return 1;
13314     }
13315     return 0;
13316 }
13317
13318 void
13319 TwoMachinesEvent P((void))
13320 {
13321     int i;
13322     char buf[MSG_SIZ];
13323     ChessProgramState *onmove;
13324     char *bookHit = NULL;
13325     static int stalling = 0;
13326     TimeMark now;
13327     long wait;
13328
13329     if (appData.noChessProgram) return;
13330
13331     switch (gameMode) {
13332       case TwoMachinesPlay:
13333         return;
13334       case MachinePlaysWhite:
13335       case MachinePlaysBlack:
13336         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13337             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13338             return;
13339         }
13340         /* fall through */
13341       case BeginningOfGame:
13342       case PlayFromGameFile:
13343       case EndOfGame:
13344         EditGameEvent();
13345         if (gameMode != EditGame) return;
13346         break;
13347       case EditPosition:
13348         EditPositionDone(TRUE);
13349         break;
13350       case AnalyzeMode:
13351       case AnalyzeFile:
13352         ExitAnalyzeMode();
13353         break;
13354       case EditGame:
13355       default:
13356         break;
13357     }
13358
13359 //    forwardMostMove = currentMove;
13360     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13361
13362     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13363
13364     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13365     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13366       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13367       return;
13368     }
13369     if(!stalling) {
13370       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13371       SendToProgram("force\n", &second);
13372       stalling = 1;
13373       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13374       return;
13375     }
13376     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13377     if(appData.matchPause>10000 || appData.matchPause<10)
13378                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13379     wait = SubtractTimeMarks(&now, &pauseStart);
13380     if(wait < appData.matchPause) {
13381         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13382         return;
13383     }
13384     stalling = 0;
13385     DisplayMessage("", "");
13386     if (startedFromSetupPosition) {
13387         SendBoard(&second, backwardMostMove);
13388     if (appData.debugMode) {
13389         fprintf(debugFP, "Two Machines\n");
13390     }
13391     }
13392     for (i = backwardMostMove; i < forwardMostMove; i++) {
13393         SendMoveToProgram(i, &second);
13394     }
13395
13396     gameMode = TwoMachinesPlay;
13397     pausing = FALSE;
13398     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13399     SetGameInfo();
13400     DisplayTwoMachinesTitle();
13401     firstMove = TRUE;
13402     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13403         onmove = &first;
13404     } else {
13405         onmove = &second;
13406     }
13407     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13408     SendToProgram(first.computerString, &first);
13409     if (first.sendName) {
13410       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13411       SendToProgram(buf, &first);
13412     }
13413     SendToProgram(second.computerString, &second);
13414     if (second.sendName) {
13415       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13416       SendToProgram(buf, &second);
13417     }
13418
13419     ResetClocks();
13420     if (!first.sendTime || !second.sendTime) {
13421         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13422         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13423     }
13424     if (onmove->sendTime) {
13425       if (onmove->useColors) {
13426         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13427       }
13428       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13429     }
13430     if (onmove->useColors) {
13431       SendToProgram(onmove->twoMachinesColor, onmove);
13432     }
13433     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13434 //    SendToProgram("go\n", onmove);
13435     onmove->maybeThinking = TRUE;
13436     SetMachineThinkingEnables();
13437
13438     StartClocks();
13439
13440     if(bookHit) { // [HGM] book: simulate book reply
13441         static char bookMove[MSG_SIZ]; // a bit generous?
13442
13443         programStats.nodes = programStats.depth = programStats.time =
13444         programStats.score = programStats.got_only_move = 0;
13445         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13446
13447         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13448         strcat(bookMove, bookHit);
13449         savedMessage = bookMove; // args for deferred call
13450         savedState = onmove;
13451         ScheduleDelayedEvent(DeferredBookMove, 1);
13452     }
13453 }
13454
13455 void
13456 TrainingEvent ()
13457 {
13458     if (gameMode == Training) {
13459       SetTrainingModeOff();
13460       gameMode = PlayFromGameFile;
13461       DisplayMessage("", _("Training mode off"));
13462     } else {
13463       gameMode = Training;
13464       animateTraining = appData.animate;
13465
13466       /* make sure we are not already at the end of the game */
13467       if (currentMove < forwardMostMove) {
13468         SetTrainingModeOn();
13469         DisplayMessage("", _("Training mode on"));
13470       } else {
13471         gameMode = PlayFromGameFile;
13472         DisplayError(_("Already at end of game"), 0);
13473       }
13474     }
13475     ModeHighlight();
13476 }
13477
13478 void
13479 IcsClientEvent ()
13480 {
13481     if (!appData.icsActive) return;
13482     switch (gameMode) {
13483       case IcsPlayingWhite:
13484       case IcsPlayingBlack:
13485       case IcsObserving:
13486       case IcsIdle:
13487       case BeginningOfGame:
13488       case IcsExamining:
13489         return;
13490
13491       case EditGame:
13492         break;
13493
13494       case EditPosition:
13495         EditPositionDone(TRUE);
13496         break;
13497
13498       case AnalyzeMode:
13499       case AnalyzeFile:
13500         ExitAnalyzeMode();
13501         break;
13502
13503       default:
13504         EditGameEvent();
13505         break;
13506     }
13507
13508     gameMode = IcsIdle;
13509     ModeHighlight();
13510     return;
13511 }
13512
13513 void
13514 EditGameEvent ()
13515 {
13516     int i;
13517
13518     switch (gameMode) {
13519       case Training:
13520         SetTrainingModeOff();
13521         break;
13522       case MachinePlaysWhite:
13523       case MachinePlaysBlack:
13524       case BeginningOfGame:
13525         SendToProgram("force\n", &first);
13526         SetUserThinkingEnables();
13527         break;
13528       case PlayFromGameFile:
13529         (void) StopLoadGameTimer();
13530         if (gameFileFP != NULL) {
13531             gameFileFP = NULL;
13532         }
13533         break;
13534       case EditPosition:
13535         EditPositionDone(TRUE);
13536         break;
13537       case AnalyzeMode:
13538       case AnalyzeFile:
13539         ExitAnalyzeMode();
13540         SendToProgram("force\n", &first);
13541         break;
13542       case TwoMachinesPlay:
13543         GameEnds(EndOfFile, NULL, GE_PLAYER);
13544         ResurrectChessProgram();
13545         SetUserThinkingEnables();
13546         break;
13547       case EndOfGame:
13548         ResurrectChessProgram();
13549         break;
13550       case IcsPlayingBlack:
13551       case IcsPlayingWhite:
13552         DisplayError(_("Warning: You are still playing a game"), 0);
13553         break;
13554       case IcsObserving:
13555         DisplayError(_("Warning: You are still observing a game"), 0);
13556         break;
13557       case IcsExamining:
13558         DisplayError(_("Warning: You are still examining a game"), 0);
13559         break;
13560       case IcsIdle:
13561         break;
13562       case EditGame:
13563       default:
13564         return;
13565     }
13566
13567     pausing = FALSE;
13568     StopClocks();
13569     first.offeredDraw = second.offeredDraw = 0;
13570
13571     if (gameMode == PlayFromGameFile) {
13572         whiteTimeRemaining = timeRemaining[0][currentMove];
13573         blackTimeRemaining = timeRemaining[1][currentMove];
13574         DisplayTitle("");
13575     }
13576
13577     if (gameMode == MachinePlaysWhite ||
13578         gameMode == MachinePlaysBlack ||
13579         gameMode == TwoMachinesPlay ||
13580         gameMode == EndOfGame) {
13581         i = forwardMostMove;
13582         while (i > currentMove) {
13583             SendToProgram("undo\n", &first);
13584             i--;
13585         }
13586         if(!adjustedClock) {
13587         whiteTimeRemaining = timeRemaining[0][currentMove];
13588         blackTimeRemaining = timeRemaining[1][currentMove];
13589         DisplayBothClocks();
13590         }
13591         if (whiteFlag || blackFlag) {
13592             whiteFlag = blackFlag = 0;
13593         }
13594         DisplayTitle("");
13595     }
13596
13597     gameMode = EditGame;
13598     ModeHighlight();
13599     SetGameInfo();
13600 }
13601
13602
13603 void
13604 EditPositionEvent ()
13605 {
13606     if (gameMode == EditPosition) {
13607         EditGameEvent();
13608         return;
13609     }
13610
13611     EditGameEvent();
13612     if (gameMode != EditGame) return;
13613
13614     gameMode = EditPosition;
13615     ModeHighlight();
13616     SetGameInfo();
13617     if (currentMove > 0)
13618       CopyBoard(boards[0], boards[currentMove]);
13619
13620     blackPlaysFirst = !WhiteOnMove(currentMove);
13621     ResetClocks();
13622     currentMove = forwardMostMove = backwardMostMove = 0;
13623     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13624     DisplayMove(-1);
13625 }
13626
13627 void
13628 ExitAnalyzeMode ()
13629 {
13630     /* [DM] icsEngineAnalyze - possible call from other functions */
13631     if (appData.icsEngineAnalyze) {
13632         appData.icsEngineAnalyze = FALSE;
13633
13634         DisplayMessage("",_("Close ICS engine analyze..."));
13635     }
13636     if (first.analysisSupport && first.analyzing) {
13637       SendToProgram("exit\n", &first);
13638       first.analyzing = FALSE;
13639     }
13640     thinkOutput[0] = NULLCHAR;
13641 }
13642
13643 void
13644 EditPositionDone (Boolean fakeRights)
13645 {
13646     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13647
13648     startedFromSetupPosition = TRUE;
13649     InitChessProgram(&first, FALSE);
13650     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13651       boards[0][EP_STATUS] = EP_NONE;
13652       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13653     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13654         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13655         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13656       } else boards[0][CASTLING][2] = NoRights;
13657     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13658         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13659         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13660       } else boards[0][CASTLING][5] = NoRights;
13661     }
13662     SendToProgram("force\n", &first);
13663     if (blackPlaysFirst) {
13664         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13665         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13666         currentMove = forwardMostMove = backwardMostMove = 1;
13667         CopyBoard(boards[1], boards[0]);
13668     } else {
13669         currentMove = forwardMostMove = backwardMostMove = 0;
13670     }
13671     SendBoard(&first, forwardMostMove);
13672     if (appData.debugMode) {
13673         fprintf(debugFP, "EditPosDone\n");
13674     }
13675     DisplayTitle("");
13676     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13677     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13678     gameMode = EditGame;
13679     ModeHighlight();
13680     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13681     ClearHighlights(); /* [AS] */
13682 }
13683
13684 /* Pause for `ms' milliseconds */
13685 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13686 void
13687 TimeDelay (long ms)
13688 {
13689     TimeMark m1, m2;
13690
13691     GetTimeMark(&m1);
13692     do {
13693         GetTimeMark(&m2);
13694     } while (SubtractTimeMarks(&m2, &m1) < ms);
13695 }
13696
13697 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13698 void
13699 SendMultiLineToICS (char *buf)
13700 {
13701     char temp[MSG_SIZ+1], *p;
13702     int len;
13703
13704     len = strlen(buf);
13705     if (len > MSG_SIZ)
13706       len = MSG_SIZ;
13707
13708     strncpy(temp, buf, len);
13709     temp[len] = 0;
13710
13711     p = temp;
13712     while (*p) {
13713         if (*p == '\n' || *p == '\r')
13714           *p = ' ';
13715         ++p;
13716     }
13717
13718     strcat(temp, "\n");
13719     SendToICS(temp);
13720     SendToPlayer(temp, strlen(temp));
13721 }
13722
13723 void
13724 SetWhiteToPlayEvent ()
13725 {
13726     if (gameMode == EditPosition) {
13727         blackPlaysFirst = FALSE;
13728         DisplayBothClocks();    /* works because currentMove is 0 */
13729     } else if (gameMode == IcsExamining) {
13730         SendToICS(ics_prefix);
13731         SendToICS("tomove white\n");
13732     }
13733 }
13734
13735 void
13736 SetBlackToPlayEvent ()
13737 {
13738     if (gameMode == EditPosition) {
13739         blackPlaysFirst = TRUE;
13740         currentMove = 1;        /* kludge */
13741         DisplayBothClocks();
13742         currentMove = 0;
13743     } else if (gameMode == IcsExamining) {
13744         SendToICS(ics_prefix);
13745         SendToICS("tomove black\n");
13746     }
13747 }
13748
13749 void
13750 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13751 {
13752     char buf[MSG_SIZ];
13753     ChessSquare piece = boards[0][y][x];
13754
13755     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13756
13757     switch (selection) {
13758       case ClearBoard:
13759         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13760             SendToICS(ics_prefix);
13761             SendToICS("bsetup clear\n");
13762         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13763             SendToICS(ics_prefix);
13764             SendToICS("clearboard\n");
13765         } else {
13766             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13767                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13768                 for (y = 0; y < BOARD_HEIGHT; y++) {
13769                     if (gameMode == IcsExamining) {
13770                         if (boards[currentMove][y][x] != EmptySquare) {
13771                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13772                                     AAA + x, ONE + y);
13773                             SendToICS(buf);
13774                         }
13775                     } else {
13776                         boards[0][y][x] = p;
13777                     }
13778                 }
13779             }
13780         }
13781         if (gameMode == EditPosition) {
13782             DrawPosition(FALSE, boards[0]);
13783         }
13784         break;
13785
13786       case WhitePlay:
13787         SetWhiteToPlayEvent();
13788         break;
13789
13790       case BlackPlay:
13791         SetBlackToPlayEvent();
13792         break;
13793
13794       case EmptySquare:
13795         if (gameMode == IcsExamining) {
13796             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13797             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13798             SendToICS(buf);
13799         } else {
13800             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13801                 if(x == BOARD_LEFT-2) {
13802                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13803                     boards[0][y][1] = 0;
13804                 } else
13805                 if(x == BOARD_RGHT+1) {
13806                     if(y >= gameInfo.holdingsSize) break;
13807                     boards[0][y][BOARD_WIDTH-2] = 0;
13808                 } else break;
13809             }
13810             boards[0][y][x] = EmptySquare;
13811             DrawPosition(FALSE, boards[0]);
13812         }
13813         break;
13814
13815       case PromotePiece:
13816         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13817            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13818             selection = (ChessSquare) (PROMOTED piece);
13819         } else if(piece == EmptySquare) selection = WhiteSilver;
13820         else selection = (ChessSquare)((int)piece - 1);
13821         goto defaultlabel;
13822
13823       case DemotePiece:
13824         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13825            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13826             selection = (ChessSquare) (DEMOTED piece);
13827         } else if(piece == EmptySquare) selection = BlackSilver;
13828         else selection = (ChessSquare)((int)piece + 1);
13829         goto defaultlabel;
13830
13831       case WhiteQueen:
13832       case BlackQueen:
13833         if(gameInfo.variant == VariantShatranj ||
13834            gameInfo.variant == VariantXiangqi  ||
13835            gameInfo.variant == VariantCourier  ||
13836            gameInfo.variant == VariantMakruk     )
13837             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13838         goto defaultlabel;
13839
13840       case WhiteKing:
13841       case BlackKing:
13842         if(gameInfo.variant == VariantXiangqi)
13843             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13844         if(gameInfo.variant == VariantKnightmate)
13845             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13846       default:
13847         defaultlabel:
13848         if (gameMode == IcsExamining) {
13849             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13850             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13851                      PieceToChar(selection), AAA + x, ONE + y);
13852             SendToICS(buf);
13853         } else {
13854             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13855                 int n;
13856                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13857                     n = PieceToNumber(selection - BlackPawn);
13858                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13859                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13860                     boards[0][BOARD_HEIGHT-1-n][1]++;
13861                 } else
13862                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13863                     n = PieceToNumber(selection);
13864                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13865                     boards[0][n][BOARD_WIDTH-1] = selection;
13866                     boards[0][n][BOARD_WIDTH-2]++;
13867                 }
13868             } else
13869             boards[0][y][x] = selection;
13870             DrawPosition(TRUE, boards[0]);
13871         }
13872         break;
13873     }
13874 }
13875
13876
13877 void
13878 DropMenuEvent (ChessSquare selection, int x, int y)
13879 {
13880     ChessMove moveType;
13881
13882     switch (gameMode) {
13883       case IcsPlayingWhite:
13884       case MachinePlaysBlack:
13885         if (!WhiteOnMove(currentMove)) {
13886             DisplayMoveError(_("It is Black's turn"));
13887             return;
13888         }
13889         moveType = WhiteDrop;
13890         break;
13891       case IcsPlayingBlack:
13892       case MachinePlaysWhite:
13893         if (WhiteOnMove(currentMove)) {
13894             DisplayMoveError(_("It is White's turn"));
13895             return;
13896         }
13897         moveType = BlackDrop;
13898         break;
13899       case EditGame:
13900         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13901         break;
13902       default:
13903         return;
13904     }
13905
13906     if (moveType == BlackDrop && selection < BlackPawn) {
13907       selection = (ChessSquare) ((int) selection
13908                                  + (int) BlackPawn - (int) WhitePawn);
13909     }
13910     if (boards[currentMove][y][x] != EmptySquare) {
13911         DisplayMoveError(_("That square is occupied"));
13912         return;
13913     }
13914
13915     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13916 }
13917
13918 void
13919 AcceptEvent ()
13920 {
13921     /* Accept a pending offer of any kind from opponent */
13922
13923     if (appData.icsActive) {
13924         SendToICS(ics_prefix);
13925         SendToICS("accept\n");
13926     } else if (cmailMsgLoaded) {
13927         if (currentMove == cmailOldMove &&
13928             commentList[cmailOldMove] != NULL &&
13929             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13930                    "Black offers a draw" : "White offers a draw")) {
13931             TruncateGame();
13932             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13933             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13934         } else {
13935             DisplayError(_("There is no pending offer on this move"), 0);
13936             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13937         }
13938     } else {
13939         /* Not used for offers from chess program */
13940     }
13941 }
13942
13943 void
13944 DeclineEvent ()
13945 {
13946     /* Decline a pending offer of any kind from opponent */
13947
13948     if (appData.icsActive) {
13949         SendToICS(ics_prefix);
13950         SendToICS("decline\n");
13951     } else if (cmailMsgLoaded) {
13952         if (currentMove == cmailOldMove &&
13953             commentList[cmailOldMove] != NULL &&
13954             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13955                    "Black offers a draw" : "White offers a draw")) {
13956 #ifdef NOTDEF
13957             AppendComment(cmailOldMove, "Draw declined", TRUE);
13958             DisplayComment(cmailOldMove - 1, "Draw declined");
13959 #endif /*NOTDEF*/
13960         } else {
13961             DisplayError(_("There is no pending offer on this move"), 0);
13962         }
13963     } else {
13964         /* Not used for offers from chess program */
13965     }
13966 }
13967
13968 void
13969 RematchEvent ()
13970 {
13971     /* Issue ICS rematch command */
13972     if (appData.icsActive) {
13973         SendToICS(ics_prefix);
13974         SendToICS("rematch\n");
13975     }
13976 }
13977
13978 void
13979 CallFlagEvent ()
13980 {
13981     /* Call your opponent's flag (claim a win on time) */
13982     if (appData.icsActive) {
13983         SendToICS(ics_prefix);
13984         SendToICS("flag\n");
13985     } else {
13986         switch (gameMode) {
13987           default:
13988             return;
13989           case MachinePlaysWhite:
13990             if (whiteFlag) {
13991                 if (blackFlag)
13992                   GameEnds(GameIsDrawn, "Both players ran out of time",
13993                            GE_PLAYER);
13994                 else
13995                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13996             } else {
13997                 DisplayError(_("Your opponent is not out of time"), 0);
13998             }
13999             break;
14000           case MachinePlaysBlack:
14001             if (blackFlag) {
14002                 if (whiteFlag)
14003                   GameEnds(GameIsDrawn, "Both players ran out of time",
14004                            GE_PLAYER);
14005                 else
14006                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14007             } else {
14008                 DisplayError(_("Your opponent is not out of time"), 0);
14009             }
14010             break;
14011         }
14012     }
14013 }
14014
14015 void
14016 ClockClick (int which)
14017 {       // [HGM] code moved to back-end from winboard.c
14018         if(which) { // black clock
14019           if (gameMode == EditPosition || gameMode == IcsExamining) {
14020             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14021             SetBlackToPlayEvent();
14022           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14023           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14024           } else if (shiftKey) {
14025             AdjustClock(which, -1);
14026           } else if (gameMode == IcsPlayingWhite ||
14027                      gameMode == MachinePlaysBlack) {
14028             CallFlagEvent();
14029           }
14030         } else { // white clock
14031           if (gameMode == EditPosition || gameMode == IcsExamining) {
14032             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14033             SetWhiteToPlayEvent();
14034           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14035           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14036           } else if (shiftKey) {
14037             AdjustClock(which, -1);
14038           } else if (gameMode == IcsPlayingBlack ||
14039                    gameMode == MachinePlaysWhite) {
14040             CallFlagEvent();
14041           }
14042         }
14043 }
14044
14045 void
14046 DrawEvent ()
14047 {
14048     /* Offer draw or accept pending draw offer from opponent */
14049
14050     if (appData.icsActive) {
14051         /* Note: tournament rules require draw offers to be
14052            made after you make your move but before you punch
14053            your clock.  Currently ICS doesn't let you do that;
14054            instead, you immediately punch your clock after making
14055            a move, but you can offer a draw at any time. */
14056
14057         SendToICS(ics_prefix);
14058         SendToICS("draw\n");
14059         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14060     } else if (cmailMsgLoaded) {
14061         if (currentMove == cmailOldMove &&
14062             commentList[cmailOldMove] != NULL &&
14063             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14064                    "Black offers a draw" : "White offers a draw")) {
14065             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14066             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14067         } else if (currentMove == cmailOldMove + 1) {
14068             char *offer = WhiteOnMove(cmailOldMove) ?
14069               "White offers a draw" : "Black offers a draw";
14070             AppendComment(currentMove, offer, TRUE);
14071             DisplayComment(currentMove - 1, offer);
14072             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14073         } else {
14074             DisplayError(_("You must make your move before offering a draw"), 0);
14075             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14076         }
14077     } else if (first.offeredDraw) {
14078         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14079     } else {
14080         if (first.sendDrawOffers) {
14081             SendToProgram("draw\n", &first);
14082             userOfferedDraw = TRUE;
14083         }
14084     }
14085 }
14086
14087 void
14088 AdjournEvent ()
14089 {
14090     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14091
14092     if (appData.icsActive) {
14093         SendToICS(ics_prefix);
14094         SendToICS("adjourn\n");
14095     } else {
14096         /* Currently GNU Chess doesn't offer or accept Adjourns */
14097     }
14098 }
14099
14100
14101 void
14102 AbortEvent ()
14103 {
14104     /* Offer Abort or accept pending Abort offer from opponent */
14105
14106     if (appData.icsActive) {
14107         SendToICS(ics_prefix);
14108         SendToICS("abort\n");
14109     } else {
14110         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14111     }
14112 }
14113
14114 void
14115 ResignEvent ()
14116 {
14117     /* Resign.  You can do this even if it's not your turn. */
14118
14119     if (appData.icsActive) {
14120         SendToICS(ics_prefix);
14121         SendToICS("resign\n");
14122     } else {
14123         switch (gameMode) {
14124           case MachinePlaysWhite:
14125             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14126             break;
14127           case MachinePlaysBlack:
14128             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14129             break;
14130           case EditGame:
14131             if (cmailMsgLoaded) {
14132                 TruncateGame();
14133                 if (WhiteOnMove(cmailOldMove)) {
14134                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14135                 } else {
14136                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14137                 }
14138                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14139             }
14140             break;
14141           default:
14142             break;
14143         }
14144     }
14145 }
14146
14147
14148 void
14149 StopObservingEvent ()
14150 {
14151     /* Stop observing current games */
14152     SendToICS(ics_prefix);
14153     SendToICS("unobserve\n");
14154 }
14155
14156 void
14157 StopExaminingEvent ()
14158 {
14159     /* Stop observing current game */
14160     SendToICS(ics_prefix);
14161     SendToICS("unexamine\n");
14162 }
14163
14164 void
14165 ForwardInner (int target)
14166 {
14167     int limit;
14168
14169     if (appData.debugMode)
14170         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14171                 target, currentMove, forwardMostMove);
14172
14173     if (gameMode == EditPosition)
14174       return;
14175
14176     seekGraphUp = FALSE;
14177     MarkTargetSquares(1);
14178
14179     if (gameMode == PlayFromGameFile && !pausing)
14180       PauseEvent();
14181
14182     if (gameMode == IcsExamining && pausing)
14183       limit = pauseExamForwardMostMove;
14184     else
14185       limit = forwardMostMove;
14186
14187     if (target > limit) target = limit;
14188
14189     if (target > 0 && moveList[target - 1][0]) {
14190         int fromX, fromY, toX, toY;
14191         toX = moveList[target - 1][2] - AAA;
14192         toY = moveList[target - 1][3] - ONE;
14193         if (moveList[target - 1][1] == '@') {
14194             if (appData.highlightLastMove) {
14195                 SetHighlights(-1, -1, toX, toY);
14196             }
14197         } else {
14198             fromX = moveList[target - 1][0] - AAA;
14199             fromY = moveList[target - 1][1] - ONE;
14200             if (target == currentMove + 1) {
14201                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14202             }
14203             if (appData.highlightLastMove) {
14204                 SetHighlights(fromX, fromY, toX, toY);
14205             }
14206         }
14207     }
14208     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14209         gameMode == Training || gameMode == PlayFromGameFile ||
14210         gameMode == AnalyzeFile) {
14211         while (currentMove < target) {
14212             SendMoveToProgram(currentMove++, &first);
14213         }
14214     } else {
14215         currentMove = target;
14216     }
14217
14218     if (gameMode == EditGame || gameMode == EndOfGame) {
14219         whiteTimeRemaining = timeRemaining[0][currentMove];
14220         blackTimeRemaining = timeRemaining[1][currentMove];
14221     }
14222     DisplayBothClocks();
14223     DisplayMove(currentMove - 1);
14224     DrawPosition(FALSE, boards[currentMove]);
14225     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14226     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14227         DisplayComment(currentMove - 1, commentList[currentMove]);
14228     }
14229 }
14230
14231
14232 void
14233 ForwardEvent ()
14234 {
14235     if (gameMode == IcsExamining && !pausing) {
14236         SendToICS(ics_prefix);
14237         SendToICS("forward\n");
14238     } else {
14239         ForwardInner(currentMove + 1);
14240     }
14241 }
14242
14243 void
14244 ToEndEvent ()
14245 {
14246     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14247         /* to optimze, we temporarily turn off analysis mode while we feed
14248          * the remaining moves to the engine. Otherwise we get analysis output
14249          * after each move.
14250          */
14251         if (first.analysisSupport) {
14252           SendToProgram("exit\nforce\n", &first);
14253           first.analyzing = FALSE;
14254         }
14255     }
14256
14257     if (gameMode == IcsExamining && !pausing) {
14258         SendToICS(ics_prefix);
14259         SendToICS("forward 999999\n");
14260     } else {
14261         ForwardInner(forwardMostMove);
14262     }
14263
14264     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14265         /* we have fed all the moves, so reactivate analysis mode */
14266         SendToProgram("analyze\n", &first);
14267         first.analyzing = TRUE;
14268         /*first.maybeThinking = TRUE;*/
14269         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14270     }
14271 }
14272
14273 void
14274 BackwardInner (int target)
14275 {
14276     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14277
14278     if (appData.debugMode)
14279         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14280                 target, currentMove, forwardMostMove);
14281
14282     if (gameMode == EditPosition) return;
14283     seekGraphUp = FALSE;
14284     MarkTargetSquares(1);
14285     if (currentMove <= backwardMostMove) {
14286         ClearHighlights();
14287         DrawPosition(full_redraw, boards[currentMove]);
14288         return;
14289     }
14290     if (gameMode == PlayFromGameFile && !pausing)
14291       PauseEvent();
14292
14293     if (moveList[target][0]) {
14294         int fromX, fromY, toX, toY;
14295         toX = moveList[target][2] - AAA;
14296         toY = moveList[target][3] - ONE;
14297         if (moveList[target][1] == '@') {
14298             if (appData.highlightLastMove) {
14299                 SetHighlights(-1, -1, toX, toY);
14300             }
14301         } else {
14302             fromX = moveList[target][0] - AAA;
14303             fromY = moveList[target][1] - ONE;
14304             if (target == currentMove - 1) {
14305                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14306             }
14307             if (appData.highlightLastMove) {
14308                 SetHighlights(fromX, fromY, toX, toY);
14309             }
14310         }
14311     }
14312     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14313         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14314         while (currentMove > target) {
14315             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14316                 // null move cannot be undone. Reload program with move history before it.
14317                 int i;
14318                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14319                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14320                 }
14321                 SendBoard(&first, i); 
14322                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14323                 break;
14324             }
14325             SendToProgram("undo\n", &first);
14326             currentMove--;
14327         }
14328     } else {
14329         currentMove = target;
14330     }
14331
14332     if (gameMode == EditGame || gameMode == EndOfGame) {
14333         whiteTimeRemaining = timeRemaining[0][currentMove];
14334         blackTimeRemaining = timeRemaining[1][currentMove];
14335     }
14336     DisplayBothClocks();
14337     DisplayMove(currentMove - 1);
14338     DrawPosition(full_redraw, boards[currentMove]);
14339     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14340     // [HGM] PV info: routine tests if comment empty
14341     DisplayComment(currentMove - 1, commentList[currentMove]);
14342 }
14343
14344 void
14345 BackwardEvent ()
14346 {
14347     if (gameMode == IcsExamining && !pausing) {
14348         SendToICS(ics_prefix);
14349         SendToICS("backward\n");
14350     } else {
14351         BackwardInner(currentMove - 1);
14352     }
14353 }
14354
14355 void
14356 ToStartEvent ()
14357 {
14358     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14359         /* to optimize, we temporarily turn off analysis mode while we undo
14360          * all the moves. Otherwise we get analysis output after each undo.
14361          */
14362         if (first.analysisSupport) {
14363           SendToProgram("exit\nforce\n", &first);
14364           first.analyzing = FALSE;
14365         }
14366     }
14367
14368     if (gameMode == IcsExamining && !pausing) {
14369         SendToICS(ics_prefix);
14370         SendToICS("backward 999999\n");
14371     } else {
14372         BackwardInner(backwardMostMove);
14373     }
14374
14375     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14376         /* we have fed all the moves, so reactivate analysis mode */
14377         SendToProgram("analyze\n", &first);
14378         first.analyzing = TRUE;
14379         /*first.maybeThinking = TRUE;*/
14380         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14381     }
14382 }
14383
14384 void
14385 ToNrEvent (int to)
14386 {
14387   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14388   if (to >= forwardMostMove) to = forwardMostMove;
14389   if (to <= backwardMostMove) to = backwardMostMove;
14390   if (to < currentMove) {
14391     BackwardInner(to);
14392   } else {
14393     ForwardInner(to);
14394   }
14395 }
14396
14397 void
14398 RevertEvent (Boolean annotate)
14399 {
14400     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14401         return;
14402     }
14403     if (gameMode != IcsExamining) {
14404         DisplayError(_("You are not examining a game"), 0);
14405         return;
14406     }
14407     if (pausing) {
14408         DisplayError(_("You can't revert while pausing"), 0);
14409         return;
14410     }
14411     SendToICS(ics_prefix);
14412     SendToICS("revert\n");
14413 }
14414
14415 void
14416 RetractMoveEvent ()
14417 {
14418     switch (gameMode) {
14419       case MachinePlaysWhite:
14420       case MachinePlaysBlack:
14421         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14422             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14423             return;
14424         }
14425         if (forwardMostMove < 2) return;
14426         currentMove = forwardMostMove = forwardMostMove - 2;
14427         whiteTimeRemaining = timeRemaining[0][currentMove];
14428         blackTimeRemaining = timeRemaining[1][currentMove];
14429         DisplayBothClocks();
14430         DisplayMove(currentMove - 1);
14431         ClearHighlights();/*!! could figure this out*/
14432         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14433         SendToProgram("remove\n", &first);
14434         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14435         break;
14436
14437       case BeginningOfGame:
14438       default:
14439         break;
14440
14441       case IcsPlayingWhite:
14442       case IcsPlayingBlack:
14443         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14444             SendToICS(ics_prefix);
14445             SendToICS("takeback 2\n");
14446         } else {
14447             SendToICS(ics_prefix);
14448             SendToICS("takeback 1\n");
14449         }
14450         break;
14451     }
14452 }
14453
14454 void
14455 MoveNowEvent ()
14456 {
14457     ChessProgramState *cps;
14458
14459     switch (gameMode) {
14460       case MachinePlaysWhite:
14461         if (!WhiteOnMove(forwardMostMove)) {
14462             DisplayError(_("It is your turn"), 0);
14463             return;
14464         }
14465         cps = &first;
14466         break;
14467       case MachinePlaysBlack:
14468         if (WhiteOnMove(forwardMostMove)) {
14469             DisplayError(_("It is your turn"), 0);
14470             return;
14471         }
14472         cps = &first;
14473         break;
14474       case TwoMachinesPlay:
14475         if (WhiteOnMove(forwardMostMove) ==
14476             (first.twoMachinesColor[0] == 'w')) {
14477             cps = &first;
14478         } else {
14479             cps = &second;
14480         }
14481         break;
14482       case BeginningOfGame:
14483       default:
14484         return;
14485     }
14486     SendToProgram("?\n", cps);
14487 }
14488
14489 void
14490 TruncateGameEvent ()
14491 {
14492     EditGameEvent();
14493     if (gameMode != EditGame) return;
14494     TruncateGame();
14495 }
14496
14497 void
14498 TruncateGame ()
14499 {
14500     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14501     if (forwardMostMove > currentMove) {
14502         if (gameInfo.resultDetails != NULL) {
14503             free(gameInfo.resultDetails);
14504             gameInfo.resultDetails = NULL;
14505             gameInfo.result = GameUnfinished;
14506         }
14507         forwardMostMove = currentMove;
14508         HistorySet(parseList, backwardMostMove, forwardMostMove,
14509                    currentMove-1);
14510     }
14511 }
14512
14513 void
14514 HintEvent ()
14515 {
14516     if (appData.noChessProgram) return;
14517     switch (gameMode) {
14518       case MachinePlaysWhite:
14519         if (WhiteOnMove(forwardMostMove)) {
14520             DisplayError(_("Wait until your turn"), 0);
14521             return;
14522         }
14523         break;
14524       case BeginningOfGame:
14525       case MachinePlaysBlack:
14526         if (!WhiteOnMove(forwardMostMove)) {
14527             DisplayError(_("Wait until your turn"), 0);
14528             return;
14529         }
14530         break;
14531       default:
14532         DisplayError(_("No hint available"), 0);
14533         return;
14534     }
14535     SendToProgram("hint\n", &first);
14536     hintRequested = TRUE;
14537 }
14538
14539 void
14540 BookEvent ()
14541 {
14542     if (appData.noChessProgram) return;
14543     switch (gameMode) {
14544       case MachinePlaysWhite:
14545         if (WhiteOnMove(forwardMostMove)) {
14546             DisplayError(_("Wait until your turn"), 0);
14547             return;
14548         }
14549         break;
14550       case BeginningOfGame:
14551       case MachinePlaysBlack:
14552         if (!WhiteOnMove(forwardMostMove)) {
14553             DisplayError(_("Wait until your turn"), 0);
14554             return;
14555         }
14556         break;
14557       case EditPosition:
14558         EditPositionDone(TRUE);
14559         break;
14560       case TwoMachinesPlay:
14561         return;
14562       default:
14563         break;
14564     }
14565     SendToProgram("bk\n", &first);
14566     bookOutput[0] = NULLCHAR;
14567     bookRequested = TRUE;
14568 }
14569
14570 void
14571 AboutGameEvent ()
14572 {
14573     char *tags = PGNTags(&gameInfo);
14574     TagsPopUp(tags, CmailMsg());
14575     free(tags);
14576 }
14577
14578 /* end button procedures */
14579
14580 void
14581 PrintPosition (FILE *fp, int move)
14582 {
14583     int i, j;
14584
14585     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14586         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14587             char c = PieceToChar(boards[move][i][j]);
14588             fputc(c == 'x' ? '.' : c, fp);
14589             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14590         }
14591     }
14592     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14593       fprintf(fp, "white to play\n");
14594     else
14595       fprintf(fp, "black to play\n");
14596 }
14597
14598 void
14599 PrintOpponents (FILE *fp)
14600 {
14601     if (gameInfo.white != NULL) {
14602         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14603     } else {
14604         fprintf(fp, "\n");
14605     }
14606 }
14607
14608 /* Find last component of program's own name, using some heuristics */
14609 void
14610 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14611 {
14612     char *p, *q;
14613     int local = (strcmp(host, "localhost") == 0);
14614     while (!local && (p = strchr(prog, ';')) != NULL) {
14615         p++;
14616         while (*p == ' ') p++;
14617         prog = p;
14618     }
14619     if (*prog == '"' || *prog == '\'') {
14620         q = strchr(prog + 1, *prog);
14621     } else {
14622         q = strchr(prog, ' ');
14623     }
14624     if (q == NULL) q = prog + strlen(prog);
14625     p = q;
14626     while (p >= prog && *p != '/' && *p != '\\') p--;
14627     p++;
14628     if(p == prog && *p == '"') p++;
14629     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14630     memcpy(buf, p, q - p);
14631     buf[q - p] = NULLCHAR;
14632     if (!local) {
14633         strcat(buf, "@");
14634         strcat(buf, host);
14635     }
14636 }
14637
14638 char *
14639 TimeControlTagValue ()
14640 {
14641     char buf[MSG_SIZ];
14642     if (!appData.clockMode) {
14643       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14644     } else if (movesPerSession > 0) {
14645       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14646     } else if (timeIncrement == 0) {
14647       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14648     } else {
14649       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14650     }
14651     return StrSave(buf);
14652 }
14653
14654 void
14655 SetGameInfo ()
14656 {
14657     /* This routine is used only for certain modes */
14658     VariantClass v = gameInfo.variant;
14659     ChessMove r = GameUnfinished;
14660     char *p = NULL;
14661
14662     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14663         r = gameInfo.result;
14664         p = gameInfo.resultDetails;
14665         gameInfo.resultDetails = NULL;
14666     }
14667     ClearGameInfo(&gameInfo);
14668     gameInfo.variant = v;
14669
14670     switch (gameMode) {
14671       case MachinePlaysWhite:
14672         gameInfo.event = StrSave( appData.pgnEventHeader );
14673         gameInfo.site = StrSave(HostName());
14674         gameInfo.date = PGNDate();
14675         gameInfo.round = StrSave("-");
14676         gameInfo.white = StrSave(first.tidy);
14677         gameInfo.black = StrSave(UserName());
14678         gameInfo.timeControl = TimeControlTagValue();
14679         break;
14680
14681       case MachinePlaysBlack:
14682         gameInfo.event = StrSave( appData.pgnEventHeader );
14683         gameInfo.site = StrSave(HostName());
14684         gameInfo.date = PGNDate();
14685         gameInfo.round = StrSave("-");
14686         gameInfo.white = StrSave(UserName());
14687         gameInfo.black = StrSave(first.tidy);
14688         gameInfo.timeControl = TimeControlTagValue();
14689         break;
14690
14691       case TwoMachinesPlay:
14692         gameInfo.event = StrSave( appData.pgnEventHeader );
14693         gameInfo.site = StrSave(HostName());
14694         gameInfo.date = PGNDate();
14695         if (roundNr > 0) {
14696             char buf[MSG_SIZ];
14697             snprintf(buf, MSG_SIZ, "%d", roundNr);
14698             gameInfo.round = StrSave(buf);
14699         } else {
14700             gameInfo.round = StrSave("-");
14701         }
14702         if (first.twoMachinesColor[0] == 'w') {
14703             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14704             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14705         } else {
14706             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14707             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14708         }
14709         gameInfo.timeControl = TimeControlTagValue();
14710         break;
14711
14712       case EditGame:
14713         gameInfo.event = StrSave("Edited game");
14714         gameInfo.site = StrSave(HostName());
14715         gameInfo.date = PGNDate();
14716         gameInfo.round = StrSave("-");
14717         gameInfo.white = StrSave("-");
14718         gameInfo.black = StrSave("-");
14719         gameInfo.result = r;
14720         gameInfo.resultDetails = p;
14721         break;
14722
14723       case EditPosition:
14724         gameInfo.event = StrSave("Edited position");
14725         gameInfo.site = StrSave(HostName());
14726         gameInfo.date = PGNDate();
14727         gameInfo.round = StrSave("-");
14728         gameInfo.white = StrSave("-");
14729         gameInfo.black = StrSave("-");
14730         break;
14731
14732       case IcsPlayingWhite:
14733       case IcsPlayingBlack:
14734       case IcsObserving:
14735       case IcsExamining:
14736         break;
14737
14738       case PlayFromGameFile:
14739         gameInfo.event = StrSave("Game from non-PGN file");
14740         gameInfo.site = StrSave(HostName());
14741         gameInfo.date = PGNDate();
14742         gameInfo.round = StrSave("-");
14743         gameInfo.white = StrSave("?");
14744         gameInfo.black = StrSave("?");
14745         break;
14746
14747       default:
14748         break;
14749     }
14750 }
14751
14752 void
14753 ReplaceComment (int index, char *text)
14754 {
14755     int len;
14756     char *p;
14757     float score;
14758
14759     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14760        pvInfoList[index-1].depth == len &&
14761        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14762        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14763     while (*text == '\n') text++;
14764     len = strlen(text);
14765     while (len > 0 && text[len - 1] == '\n') len--;
14766
14767     if (commentList[index] != NULL)
14768       free(commentList[index]);
14769
14770     if (len == 0) {
14771         commentList[index] = NULL;
14772         return;
14773     }
14774   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14775       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14776       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14777     commentList[index] = (char *) malloc(len + 2);
14778     strncpy(commentList[index], text, len);
14779     commentList[index][len] = '\n';
14780     commentList[index][len + 1] = NULLCHAR;
14781   } else {
14782     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14783     char *p;
14784     commentList[index] = (char *) malloc(len + 7);
14785     safeStrCpy(commentList[index], "{\n", 3);
14786     safeStrCpy(commentList[index]+2, text, len+1);
14787     commentList[index][len+2] = NULLCHAR;
14788     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14789     strcat(commentList[index], "\n}\n");
14790   }
14791 }
14792
14793 void
14794 CrushCRs (char *text)
14795 {
14796   char *p = text;
14797   char *q = text;
14798   char ch;
14799
14800   do {
14801     ch = *p++;
14802     if (ch == '\r') continue;
14803     *q++ = ch;
14804   } while (ch != '\0');
14805 }
14806
14807 void
14808 AppendComment (int index, char *text, Boolean addBraces)
14809 /* addBraces  tells if we should add {} */
14810 {
14811     int oldlen, len;
14812     char *old;
14813
14814 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14815     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14816
14817     CrushCRs(text);
14818     while (*text == '\n') text++;
14819     len = strlen(text);
14820     while (len > 0 && text[len - 1] == '\n') len--;
14821     text[len] = NULLCHAR;
14822
14823     if (len == 0) return;
14824
14825     if (commentList[index] != NULL) {
14826       Boolean addClosingBrace = addBraces;
14827         old = commentList[index];
14828         oldlen = strlen(old);
14829         while(commentList[index][oldlen-1] ==  '\n')
14830           commentList[index][--oldlen] = NULLCHAR;
14831         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14832         safeStrCpy(commentList[index], old, oldlen + len + 6);
14833         free(old);
14834         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14835         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14836           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14837           while (*text == '\n') { text++; len--; }
14838           commentList[index][--oldlen] = NULLCHAR;
14839       }
14840         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14841         else          strcat(commentList[index], "\n");
14842         strcat(commentList[index], text);
14843         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14844         else          strcat(commentList[index], "\n");
14845     } else {
14846         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14847         if(addBraces)
14848           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14849         else commentList[index][0] = NULLCHAR;
14850         strcat(commentList[index], text);
14851         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14852         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14853     }
14854 }
14855
14856 static char *
14857 FindStr (char * text, char * sub_text)
14858 {
14859     char * result = strstr( text, sub_text );
14860
14861     if( result != NULL ) {
14862         result += strlen( sub_text );
14863     }
14864
14865     return result;
14866 }
14867
14868 /* [AS] Try to extract PV info from PGN comment */
14869 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14870 char *
14871 GetInfoFromComment (int index, char * text)
14872 {
14873     char * sep = text, *p;
14874
14875     if( text != NULL && index > 0 ) {
14876         int score = 0;
14877         int depth = 0;
14878         int time = -1, sec = 0, deci;
14879         char * s_eval = FindStr( text, "[%eval " );
14880         char * s_emt = FindStr( text, "[%emt " );
14881
14882         if( s_eval != NULL || s_emt != NULL ) {
14883             /* New style */
14884             char delim;
14885
14886             if( s_eval != NULL ) {
14887                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14888                     return text;
14889                 }
14890
14891                 if( delim != ']' ) {
14892                     return text;
14893                 }
14894             }
14895
14896             if( s_emt != NULL ) {
14897             }
14898                 return text;
14899         }
14900         else {
14901             /* We expect something like: [+|-]nnn.nn/dd */
14902             int score_lo = 0;
14903
14904             if(*text != '{') return text; // [HGM] braces: must be normal comment
14905
14906             sep = strchr( text, '/' );
14907             if( sep == NULL || sep < (text+4) ) {
14908                 return text;
14909             }
14910
14911             p = text;
14912             if(p[1] == '(') { // comment starts with PV
14913                p = strchr(p, ')'); // locate end of PV
14914                if(p == NULL || sep < p+5) return text;
14915                // at this point we have something like "{(.*) +0.23/6 ..."
14916                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14917                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14918                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14919             }
14920             time = -1; sec = -1; deci = -1;
14921             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14922                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14923                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14924                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14925                 return text;
14926             }
14927
14928             if( score_lo < 0 || score_lo >= 100 ) {
14929                 return text;
14930             }
14931
14932             if(sec >= 0) time = 600*time + 10*sec; else
14933             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14934
14935             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14936
14937             /* [HGM] PV time: now locate end of PV info */
14938             while( *++sep >= '0' && *sep <= '9'); // strip depth
14939             if(time >= 0)
14940             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14941             if(sec >= 0)
14942             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14943             if(deci >= 0)
14944             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14945             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
14946         }
14947
14948         if( depth <= 0 ) {
14949             return text;
14950         }
14951
14952         if( time < 0 ) {
14953             time = -1;
14954         }
14955
14956         pvInfoList[index-1].depth = depth;
14957         pvInfoList[index-1].score = score;
14958         pvInfoList[index-1].time  = 10*time; // centi-sec
14959         if(*sep == '}') *sep = 0; else *--sep = '{';
14960         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14961     }
14962     return sep;
14963 }
14964
14965 void
14966 SendToProgram (char *message, ChessProgramState *cps)
14967 {
14968     int count, outCount, error;
14969     char buf[MSG_SIZ];
14970
14971     if (cps->pr == NoProc) return;
14972     Attention(cps);
14973
14974     if (appData.debugMode) {
14975         TimeMark now;
14976         GetTimeMark(&now);
14977         fprintf(debugFP, "%ld >%-6s: %s",
14978                 SubtractTimeMarks(&now, &programStartTime),
14979                 cps->which, message);
14980     }
14981
14982     count = strlen(message);
14983     outCount = OutputToProcess(cps->pr, message, count, &error);
14984     if (outCount < count && !exiting
14985                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14986       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14987       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14988         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14989             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14990                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14991                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14992                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14993             } else {
14994                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14995                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14996                 gameInfo.result = res;
14997             }
14998             gameInfo.resultDetails = StrSave(buf);
14999         }
15000         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15001         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15002     }
15003 }
15004
15005 void
15006 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15007 {
15008     char *end_str;
15009     char buf[MSG_SIZ];
15010     ChessProgramState *cps = (ChessProgramState *)closure;
15011
15012     if (isr != cps->isr) return; /* Killed intentionally */
15013     if (count <= 0) {
15014         if (count == 0) {
15015             RemoveInputSource(cps->isr);
15016             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15017             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15018                     _(cps->which), cps->program);
15019         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15020                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15021                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15022                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15023                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15024                 } else {
15025                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15026                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15027                     gameInfo.result = res;
15028                 }
15029                 gameInfo.resultDetails = StrSave(buf);
15030             }
15031             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15032             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15033         } else {
15034             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15035                     _(cps->which), cps->program);
15036             RemoveInputSource(cps->isr);
15037
15038             /* [AS] Program is misbehaving badly... kill it */
15039             if( count == -2 ) {
15040                 DestroyChildProcess( cps->pr, 9 );
15041                 cps->pr = NoProc;
15042             }
15043
15044             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15045         }
15046         return;
15047     }
15048
15049     if ((end_str = strchr(message, '\r')) != NULL)
15050       *end_str = NULLCHAR;
15051     if ((end_str = strchr(message, '\n')) != NULL)
15052       *end_str = NULLCHAR;
15053
15054     if (appData.debugMode) {
15055         TimeMark now; int print = 1;
15056         char *quote = ""; char c; int i;
15057
15058         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15059                 char start = message[0];
15060                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15061                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15062                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15063                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15064                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15065                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15066                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15067                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15068                    sscanf(message, "hint: %c", &c)!=1 && 
15069                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15070                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15071                     print = (appData.engineComments >= 2);
15072                 }
15073                 message[0] = start; // restore original message
15074         }
15075         if(print) {
15076                 GetTimeMark(&now);
15077                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15078                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15079                         quote,
15080                         message);
15081         }
15082     }
15083
15084     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15085     if (appData.icsEngineAnalyze) {
15086         if (strstr(message, "whisper") != NULL ||
15087              strstr(message, "kibitz") != NULL ||
15088             strstr(message, "tellics") != NULL) return;
15089     }
15090
15091     HandleMachineMove(message, cps);
15092 }
15093
15094
15095 void
15096 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15097 {
15098     char buf[MSG_SIZ];
15099     int seconds;
15100
15101     if( timeControl_2 > 0 ) {
15102         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15103             tc = timeControl_2;
15104         }
15105     }
15106     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15107     inc /= cps->timeOdds;
15108     st  /= cps->timeOdds;
15109
15110     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15111
15112     if (st > 0) {
15113       /* Set exact time per move, normally using st command */
15114       if (cps->stKludge) {
15115         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15116         seconds = st % 60;
15117         if (seconds == 0) {
15118           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15119         } else {
15120           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15121         }
15122       } else {
15123         snprintf(buf, MSG_SIZ, "st %d\n", st);
15124       }
15125     } else {
15126       /* Set conventional or incremental time control, using level command */
15127       if (seconds == 0) {
15128         /* Note old gnuchess bug -- minutes:seconds used to not work.
15129            Fixed in later versions, but still avoid :seconds
15130            when seconds is 0. */
15131         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15132       } else {
15133         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15134                  seconds, inc/1000.);
15135       }
15136     }
15137     SendToProgram(buf, cps);
15138
15139     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15140     /* Orthogonally, limit search to given depth */
15141     if (sd > 0) {
15142       if (cps->sdKludge) {
15143         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15144       } else {
15145         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15146       }
15147       SendToProgram(buf, cps);
15148     }
15149
15150     if(cps->nps >= 0) { /* [HGM] nps */
15151         if(cps->supportsNPS == FALSE)
15152           cps->nps = -1; // don't use if engine explicitly says not supported!
15153         else {
15154           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15155           SendToProgram(buf, cps);
15156         }
15157     }
15158 }
15159
15160 ChessProgramState *
15161 WhitePlayer ()
15162 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15163 {
15164     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15165        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15166         return &second;
15167     return &first;
15168 }
15169
15170 void
15171 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15172 {
15173     char message[MSG_SIZ];
15174     long time, otime;
15175
15176     /* Note: this routine must be called when the clocks are stopped
15177        or when they have *just* been set or switched; otherwise
15178        it will be off by the time since the current tick started.
15179     */
15180     if (machineWhite) {
15181         time = whiteTimeRemaining / 10;
15182         otime = blackTimeRemaining / 10;
15183     } else {
15184         time = blackTimeRemaining / 10;
15185         otime = whiteTimeRemaining / 10;
15186     }
15187     /* [HGM] translate opponent's time by time-odds factor */
15188     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15189     if (appData.debugMode) {
15190         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15191     }
15192
15193     if (time <= 0) time = 1;
15194     if (otime <= 0) otime = 1;
15195
15196     snprintf(message, MSG_SIZ, "time %ld\n", time);
15197     SendToProgram(message, cps);
15198
15199     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15200     SendToProgram(message, cps);
15201 }
15202
15203 int
15204 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15205 {
15206   char buf[MSG_SIZ];
15207   int len = strlen(name);
15208   int val;
15209
15210   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15211     (*p) += len + 1;
15212     sscanf(*p, "%d", &val);
15213     *loc = (val != 0);
15214     while (**p && **p != ' ')
15215       (*p)++;
15216     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15217     SendToProgram(buf, cps);
15218     return TRUE;
15219   }
15220   return FALSE;
15221 }
15222
15223 int
15224 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15225 {
15226   char buf[MSG_SIZ];
15227   int len = strlen(name);
15228   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15229     (*p) += len + 1;
15230     sscanf(*p, "%d", loc);
15231     while (**p && **p != ' ') (*p)++;
15232     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15233     SendToProgram(buf, cps);
15234     return TRUE;
15235   }
15236   return FALSE;
15237 }
15238
15239 int
15240 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15241 {
15242   char buf[MSG_SIZ];
15243   int len = strlen(name);
15244   if (strncmp((*p), name, len) == 0
15245       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15246     (*p) += len + 2;
15247     sscanf(*p, "%[^\"]", loc);
15248     while (**p && **p != '\"') (*p)++;
15249     if (**p == '\"') (*p)++;
15250     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15251     SendToProgram(buf, cps);
15252     return TRUE;
15253   }
15254   return FALSE;
15255 }
15256
15257 int
15258 ParseOption (Option *opt, ChessProgramState *cps)
15259 // [HGM] options: process the string that defines an engine option, and determine
15260 // name, type, default value, and allowed value range
15261 {
15262         char *p, *q, buf[MSG_SIZ];
15263         int n, min = (-1)<<31, max = 1<<31, def;
15264
15265         if(p = strstr(opt->name, " -spin ")) {
15266             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15267             if(max < min) max = min; // enforce consistency
15268             if(def < min) def = min;
15269             if(def > max) def = max;
15270             opt->value = def;
15271             opt->min = min;
15272             opt->max = max;
15273             opt->type = Spin;
15274         } else if((p = strstr(opt->name, " -slider "))) {
15275             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15276             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15277             if(max < min) max = min; // enforce consistency
15278             if(def < min) def = min;
15279             if(def > max) def = max;
15280             opt->value = def;
15281             opt->min = min;
15282             opt->max = max;
15283             opt->type = Spin; // Slider;
15284         } else if((p = strstr(opt->name, " -string "))) {
15285             opt->textValue = p+9;
15286             opt->type = TextBox;
15287         } else if((p = strstr(opt->name, " -file "))) {
15288             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15289             opt->textValue = p+7;
15290             opt->type = FileName; // FileName;
15291         } else if((p = strstr(opt->name, " -path "))) {
15292             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15293             opt->textValue = p+7;
15294             opt->type = PathName; // PathName;
15295         } else if(p = strstr(opt->name, " -check ")) {
15296             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15297             opt->value = (def != 0);
15298             opt->type = CheckBox;
15299         } else if(p = strstr(opt->name, " -combo ")) {
15300             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15301             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15302             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15303             opt->value = n = 0;
15304             while(q = StrStr(q, " /// ")) {
15305                 n++; *q = 0;    // count choices, and null-terminate each of them
15306                 q += 5;
15307                 if(*q == '*') { // remember default, which is marked with * prefix
15308                     q++;
15309                     opt->value = n;
15310                 }
15311                 cps->comboList[cps->comboCnt++] = q;
15312             }
15313             cps->comboList[cps->comboCnt++] = NULL;
15314             opt->max = n + 1;
15315             opt->type = ComboBox;
15316         } else if(p = strstr(opt->name, " -button")) {
15317             opt->type = Button;
15318         } else if(p = strstr(opt->name, " -save")) {
15319             opt->type = SaveButton;
15320         } else return FALSE;
15321         *p = 0; // terminate option name
15322         // now look if the command-line options define a setting for this engine option.
15323         if(cps->optionSettings && cps->optionSettings[0])
15324             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15325         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15326           snprintf(buf, MSG_SIZ, "option %s", p);
15327                 if(p = strstr(buf, ",")) *p = 0;
15328                 if(q = strchr(buf, '=')) switch(opt->type) {
15329                     case ComboBox:
15330                         for(n=0; n<opt->max; n++)
15331                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15332                         break;
15333                     case TextBox:
15334                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15335                         break;
15336                     case Spin:
15337                     case CheckBox:
15338                         opt->value = atoi(q+1);
15339                     default:
15340                         break;
15341                 }
15342                 strcat(buf, "\n");
15343                 SendToProgram(buf, cps);
15344         }
15345         return TRUE;
15346 }
15347
15348 void
15349 FeatureDone (ChessProgramState *cps, int val)
15350 {
15351   DelayedEventCallback cb = GetDelayedEvent();
15352   if ((cb == InitBackEnd3 && cps == &first) ||
15353       (cb == SettingsMenuIfReady && cps == &second) ||
15354       (cb == LoadEngine) ||
15355       (cb == TwoMachinesEventIfReady)) {
15356     CancelDelayedEvent();
15357     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15358   }
15359   cps->initDone = val;
15360 }
15361
15362 /* Parse feature command from engine */
15363 void
15364 ParseFeatures (char *args, ChessProgramState *cps)
15365 {
15366   char *p = args;
15367   char *q;
15368   int val;
15369   char buf[MSG_SIZ];
15370
15371   for (;;) {
15372     while (*p == ' ') p++;
15373     if (*p == NULLCHAR) return;
15374
15375     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15376     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15377     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15378     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15379     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15380     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15381     if (BoolFeature(&p, "reuse", &val, cps)) {
15382       /* Engine can disable reuse, but can't enable it if user said no */
15383       if (!val) cps->reuse = FALSE;
15384       continue;
15385     }
15386     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15387     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15388       if (gameMode == TwoMachinesPlay) {
15389         DisplayTwoMachinesTitle();
15390       } else {
15391         DisplayTitle("");
15392       }
15393       continue;
15394     }
15395     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15396     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15397     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15398     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15399     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15400     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15401     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15402     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15403     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15404     if (IntFeature(&p, "done", &val, cps)) {
15405       FeatureDone(cps, val);
15406       continue;
15407     }
15408     /* Added by Tord: */
15409     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15410     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15411     /* End of additions by Tord */
15412
15413     /* [HGM] added features: */
15414     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15415     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15416     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15417     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15418     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15419     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15420     if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15421         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15422           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15423             SendToProgram(buf, cps);
15424             continue;
15425         }
15426         if(cps->nrOptions >= MAX_OPTIONS) {
15427             cps->nrOptions--;
15428             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15429             DisplayError(buf, 0);
15430         }
15431         continue;
15432     }
15433     /* End of additions by HGM */
15434
15435     /* unknown feature: complain and skip */
15436     q = p;
15437     while (*q && *q != '=') q++;
15438     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15439     SendToProgram(buf, cps);
15440     p = q;
15441     if (*p == '=') {
15442       p++;
15443       if (*p == '\"') {
15444         p++;
15445         while (*p && *p != '\"') p++;
15446         if (*p == '\"') p++;
15447       } else {
15448         while (*p && *p != ' ') p++;
15449       }
15450     }
15451   }
15452
15453 }
15454
15455 void
15456 PeriodicUpdatesEvent (int newState)
15457 {
15458     if (newState == appData.periodicUpdates)
15459       return;
15460
15461     appData.periodicUpdates=newState;
15462
15463     /* Display type changes, so update it now */
15464 //    DisplayAnalysis();
15465
15466     /* Get the ball rolling again... */
15467     if (newState) {
15468         AnalysisPeriodicEvent(1);
15469         StartAnalysisClock();
15470     }
15471 }
15472
15473 void
15474 PonderNextMoveEvent (int newState)
15475 {
15476     if (newState == appData.ponderNextMove) return;
15477     if (gameMode == EditPosition) EditPositionDone(TRUE);
15478     if (newState) {
15479         SendToProgram("hard\n", &first);
15480         if (gameMode == TwoMachinesPlay) {
15481             SendToProgram("hard\n", &second);
15482         }
15483     } else {
15484         SendToProgram("easy\n", &first);
15485         thinkOutput[0] = NULLCHAR;
15486         if (gameMode == TwoMachinesPlay) {
15487             SendToProgram("easy\n", &second);
15488         }
15489     }
15490     appData.ponderNextMove = newState;
15491 }
15492
15493 void
15494 NewSettingEvent (int option, int *feature, char *command, int value)
15495 {
15496     char buf[MSG_SIZ];
15497
15498     if (gameMode == EditPosition) EditPositionDone(TRUE);
15499     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15500     if(feature == NULL || *feature) SendToProgram(buf, &first);
15501     if (gameMode == TwoMachinesPlay) {
15502         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15503     }
15504 }
15505
15506 void
15507 ShowThinkingEvent ()
15508 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15509 {
15510     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15511     int newState = appData.showThinking
15512         // [HGM] thinking: other features now need thinking output as well
15513         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15514
15515     if (oldState == newState) return;
15516     oldState = newState;
15517     if (gameMode == EditPosition) EditPositionDone(TRUE);
15518     if (oldState) {
15519         SendToProgram("post\n", &first);
15520         if (gameMode == TwoMachinesPlay) {
15521             SendToProgram("post\n", &second);
15522         }
15523     } else {
15524         SendToProgram("nopost\n", &first);
15525         thinkOutput[0] = NULLCHAR;
15526         if (gameMode == TwoMachinesPlay) {
15527             SendToProgram("nopost\n", &second);
15528         }
15529     }
15530 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15531 }
15532
15533 void
15534 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15535 {
15536   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15537   if (pr == NoProc) return;
15538   AskQuestion(title, question, replyPrefix, pr);
15539 }
15540
15541 void
15542 TypeInEvent (char firstChar)
15543 {
15544     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15545         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15546         gameMode == AnalyzeMode || gameMode == EditGame || 
15547         gameMode == EditPosition || gameMode == IcsExamining ||
15548         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15549         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15550                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15551                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15552         gameMode == Training) PopUpMoveDialog(firstChar);
15553 }
15554
15555 void
15556 TypeInDoneEvent (char *move)
15557 {
15558         Board board;
15559         int n, fromX, fromY, toX, toY;
15560         char promoChar;
15561         ChessMove moveType;
15562
15563         // [HGM] FENedit
15564         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15565                 EditPositionPasteFEN(move);
15566                 return;
15567         }
15568         // [HGM] movenum: allow move number to be typed in any mode
15569         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15570           ToNrEvent(2*n-1);
15571           return;
15572         }
15573
15574       if (gameMode != EditGame && currentMove != forwardMostMove && 
15575         gameMode != Training) {
15576         DisplayMoveError(_("Displayed move is not current"));
15577       } else {
15578         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15579           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15580         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15581         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15582           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15583           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15584         } else {
15585           DisplayMoveError(_("Could not parse move"));
15586         }
15587       }
15588 }
15589
15590 void
15591 DisplayMove (int moveNumber)
15592 {
15593     char message[MSG_SIZ];
15594     char res[MSG_SIZ];
15595     char cpThinkOutput[MSG_SIZ];
15596
15597     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15598
15599     if (moveNumber == forwardMostMove - 1 ||
15600         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15601
15602         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15603
15604         if (strchr(cpThinkOutput, '\n')) {
15605             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15606         }
15607     } else {
15608         *cpThinkOutput = NULLCHAR;
15609     }
15610
15611     /* [AS] Hide thinking from human user */
15612     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15613         *cpThinkOutput = NULLCHAR;
15614         if( thinkOutput[0] != NULLCHAR ) {
15615             int i;
15616
15617             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15618                 cpThinkOutput[i] = '.';
15619             }
15620             cpThinkOutput[i] = NULLCHAR;
15621             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15622         }
15623     }
15624
15625     if (moveNumber == forwardMostMove - 1 &&
15626         gameInfo.resultDetails != NULL) {
15627         if (gameInfo.resultDetails[0] == NULLCHAR) {
15628           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15629         } else {
15630           snprintf(res, MSG_SIZ, " {%s} %s",
15631                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15632         }
15633     } else {
15634         res[0] = NULLCHAR;
15635     }
15636
15637     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15638         DisplayMessage(res, cpThinkOutput);
15639     } else {
15640       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15641                 WhiteOnMove(moveNumber) ? " " : ".. ",
15642                 parseList[moveNumber], res);
15643         DisplayMessage(message, cpThinkOutput);
15644     }
15645 }
15646
15647 void
15648 DisplayComment (int moveNumber, char *text)
15649 {
15650     char title[MSG_SIZ];
15651
15652     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15653       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15654     } else {
15655       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15656               WhiteOnMove(moveNumber) ? " " : ".. ",
15657               parseList[moveNumber]);
15658     }
15659     if (text != NULL && (appData.autoDisplayComment || commentUp))
15660         CommentPopUp(title, text);
15661 }
15662
15663 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15664  * might be busy thinking or pondering.  It can be omitted if your
15665  * gnuchess is configured to stop thinking immediately on any user
15666  * input.  However, that gnuchess feature depends on the FIONREAD
15667  * ioctl, which does not work properly on some flavors of Unix.
15668  */
15669 void
15670 Attention (ChessProgramState *cps)
15671 {
15672 #if ATTENTION
15673     if (!cps->useSigint) return;
15674     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15675     switch (gameMode) {
15676       case MachinePlaysWhite:
15677       case MachinePlaysBlack:
15678       case TwoMachinesPlay:
15679       case IcsPlayingWhite:
15680       case IcsPlayingBlack:
15681       case AnalyzeMode:
15682       case AnalyzeFile:
15683         /* Skip if we know it isn't thinking */
15684         if (!cps->maybeThinking) return;
15685         if (appData.debugMode)
15686           fprintf(debugFP, "Interrupting %s\n", cps->which);
15687         InterruptChildProcess(cps->pr);
15688         cps->maybeThinking = FALSE;
15689         break;
15690       default:
15691         break;
15692     }
15693 #endif /*ATTENTION*/
15694 }
15695
15696 int
15697 CheckFlags ()
15698 {
15699     if (whiteTimeRemaining <= 0) {
15700         if (!whiteFlag) {
15701             whiteFlag = TRUE;
15702             if (appData.icsActive) {
15703                 if (appData.autoCallFlag &&
15704                     gameMode == IcsPlayingBlack && !blackFlag) {
15705                   SendToICS(ics_prefix);
15706                   SendToICS("flag\n");
15707                 }
15708             } else {
15709                 if (blackFlag) {
15710                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15711                 } else {
15712                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15713                     if (appData.autoCallFlag) {
15714                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15715                         return TRUE;
15716                     }
15717                 }
15718             }
15719         }
15720     }
15721     if (blackTimeRemaining <= 0) {
15722         if (!blackFlag) {
15723             blackFlag = TRUE;
15724             if (appData.icsActive) {
15725                 if (appData.autoCallFlag &&
15726                     gameMode == IcsPlayingWhite && !whiteFlag) {
15727                   SendToICS(ics_prefix);
15728                   SendToICS("flag\n");
15729                 }
15730             } else {
15731                 if (whiteFlag) {
15732                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15733                 } else {
15734                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15735                     if (appData.autoCallFlag) {
15736                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15737                         return TRUE;
15738                     }
15739                 }
15740             }
15741         }
15742     }
15743     return FALSE;
15744 }
15745
15746 void
15747 CheckTimeControl ()
15748 {
15749     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15750         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15751
15752     /*
15753      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15754      */
15755     if ( !WhiteOnMove(forwardMostMove) ) {
15756         /* White made time control */
15757         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15758         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15759         /* [HGM] time odds: correct new time quota for time odds! */
15760                                             / WhitePlayer()->timeOdds;
15761         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15762     } else {
15763         lastBlack -= blackTimeRemaining;
15764         /* Black made time control */
15765         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15766                                             / WhitePlayer()->other->timeOdds;
15767         lastWhite = whiteTimeRemaining;
15768     }
15769 }
15770
15771 void
15772 DisplayBothClocks ()
15773 {
15774     int wom = gameMode == EditPosition ?
15775       !blackPlaysFirst : WhiteOnMove(currentMove);
15776     DisplayWhiteClock(whiteTimeRemaining, wom);
15777     DisplayBlackClock(blackTimeRemaining, !wom);
15778 }
15779
15780
15781 /* Timekeeping seems to be a portability nightmare.  I think everyone
15782    has ftime(), but I'm really not sure, so I'm including some ifdefs
15783    to use other calls if you don't.  Clocks will be less accurate if
15784    you have neither ftime nor gettimeofday.
15785 */
15786
15787 /* VS 2008 requires the #include outside of the function */
15788 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15789 #include <sys/timeb.h>
15790 #endif
15791
15792 /* Get the current time as a TimeMark */
15793 void
15794 GetTimeMark (TimeMark *tm)
15795 {
15796 #if HAVE_GETTIMEOFDAY
15797
15798     struct timeval timeVal;
15799     struct timezone timeZone;
15800
15801     gettimeofday(&timeVal, &timeZone);
15802     tm->sec = (long) timeVal.tv_sec;
15803     tm->ms = (int) (timeVal.tv_usec / 1000L);
15804
15805 #else /*!HAVE_GETTIMEOFDAY*/
15806 #if HAVE_FTIME
15807
15808 // include <sys/timeb.h> / moved to just above start of function
15809     struct timeb timeB;
15810
15811     ftime(&timeB);
15812     tm->sec = (long) timeB.time;
15813     tm->ms = (int) timeB.millitm;
15814
15815 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15816     tm->sec = (long) time(NULL);
15817     tm->ms = 0;
15818 #endif
15819 #endif
15820 }
15821
15822 /* Return the difference in milliseconds between two
15823    time marks.  We assume the difference will fit in a long!
15824 */
15825 long
15826 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
15827 {
15828     return 1000L*(tm2->sec - tm1->sec) +
15829            (long) (tm2->ms - tm1->ms);
15830 }
15831
15832
15833 /*
15834  * Code to manage the game clocks.
15835  *
15836  * In tournament play, black starts the clock and then white makes a move.
15837  * We give the human user a slight advantage if he is playing white---the
15838  * clocks don't run until he makes his first move, so it takes zero time.
15839  * Also, we don't account for network lag, so we could get out of sync
15840  * with GNU Chess's clock -- but then, referees are always right.
15841  */
15842
15843 static TimeMark tickStartTM;
15844 static long intendedTickLength;
15845
15846 long
15847 NextTickLength (long timeRemaining)
15848 {
15849     long nominalTickLength, nextTickLength;
15850
15851     if (timeRemaining > 0L && timeRemaining <= 10000L)
15852       nominalTickLength = 100L;
15853     else
15854       nominalTickLength = 1000L;
15855     nextTickLength = timeRemaining % nominalTickLength;
15856     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15857
15858     return nextTickLength;
15859 }
15860
15861 /* Adjust clock one minute up or down */
15862 void
15863 AdjustClock (Boolean which, int dir)
15864 {
15865     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15866     if(which) blackTimeRemaining += 60000*dir;
15867     else      whiteTimeRemaining += 60000*dir;
15868     DisplayBothClocks();
15869     adjustedClock = TRUE;
15870 }
15871
15872 /* Stop clocks and reset to a fresh time control */
15873 void
15874 ResetClocks ()
15875 {
15876     (void) StopClockTimer();
15877     if (appData.icsActive) {
15878         whiteTimeRemaining = blackTimeRemaining = 0;
15879     } else if (searchTime) {
15880         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15881         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15882     } else { /* [HGM] correct new time quote for time odds */
15883         whiteTC = blackTC = fullTimeControlString;
15884         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15885         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15886     }
15887     if (whiteFlag || blackFlag) {
15888         DisplayTitle("");
15889         whiteFlag = blackFlag = FALSE;
15890     }
15891     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15892     DisplayBothClocks();
15893     adjustedClock = FALSE;
15894 }
15895
15896 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15897
15898 /* Decrement running clock by amount of time that has passed */
15899 void
15900 DecrementClocks ()
15901 {
15902     long timeRemaining;
15903     long lastTickLength, fudge;
15904     TimeMark now;
15905
15906     if (!appData.clockMode) return;
15907     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15908
15909     GetTimeMark(&now);
15910
15911     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15912
15913     /* Fudge if we woke up a little too soon */
15914     fudge = intendedTickLength - lastTickLength;
15915     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15916
15917     if (WhiteOnMove(forwardMostMove)) {
15918         if(whiteNPS >= 0) lastTickLength = 0;
15919         timeRemaining = whiteTimeRemaining -= lastTickLength;
15920         if(timeRemaining < 0 && !appData.icsActive) {
15921             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15922             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15923                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15924                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15925             }
15926         }
15927         DisplayWhiteClock(whiteTimeRemaining - fudge,
15928                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15929     } else {
15930         if(blackNPS >= 0) lastTickLength = 0;
15931         timeRemaining = blackTimeRemaining -= lastTickLength;
15932         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15933             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15934             if(suddenDeath) {
15935                 blackStartMove = forwardMostMove;
15936                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15937             }
15938         }
15939         DisplayBlackClock(blackTimeRemaining - fudge,
15940                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15941     }
15942     if (CheckFlags()) return;
15943
15944     tickStartTM = now;
15945     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15946     StartClockTimer(intendedTickLength);
15947
15948     /* if the time remaining has fallen below the alarm threshold, sound the
15949      * alarm. if the alarm has sounded and (due to a takeback or time control
15950      * with increment) the time remaining has increased to a level above the
15951      * threshold, reset the alarm so it can sound again.
15952      */
15953
15954     if (appData.icsActive && appData.icsAlarm) {
15955
15956         /* make sure we are dealing with the user's clock */
15957         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15958                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15959            )) return;
15960
15961         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15962             alarmSounded = FALSE;
15963         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15964             PlayAlarmSound();
15965             alarmSounded = TRUE;
15966         }
15967     }
15968 }
15969
15970
15971 /* A player has just moved, so stop the previously running
15972    clock and (if in clock mode) start the other one.
15973    We redisplay both clocks in case we're in ICS mode, because
15974    ICS gives us an update to both clocks after every move.
15975    Note that this routine is called *after* forwardMostMove
15976    is updated, so the last fractional tick must be subtracted
15977    from the color that is *not* on move now.
15978 */
15979 void
15980 SwitchClocks (int newMoveNr)
15981 {
15982     long lastTickLength;
15983     TimeMark now;
15984     int flagged = FALSE;
15985
15986     GetTimeMark(&now);
15987
15988     if (StopClockTimer() && appData.clockMode) {
15989         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15990         if (!WhiteOnMove(forwardMostMove)) {
15991             if(blackNPS >= 0) lastTickLength = 0;
15992             blackTimeRemaining -= lastTickLength;
15993            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15994 //         if(pvInfoList[forwardMostMove].time == -1)
15995                  pvInfoList[forwardMostMove].time =               // use GUI time
15996                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15997         } else {
15998            if(whiteNPS >= 0) lastTickLength = 0;
15999            whiteTimeRemaining -= lastTickLength;
16000            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16001 //         if(pvInfoList[forwardMostMove].time == -1)
16002                  pvInfoList[forwardMostMove].time =
16003                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16004         }
16005         flagged = CheckFlags();
16006     }
16007     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16008     CheckTimeControl();
16009
16010     if (flagged || !appData.clockMode) return;
16011
16012     switch (gameMode) {
16013       case MachinePlaysBlack:
16014       case MachinePlaysWhite:
16015       case BeginningOfGame:
16016         if (pausing) return;
16017         break;
16018
16019       case EditGame:
16020       case PlayFromGameFile:
16021       case IcsExamining:
16022         return;
16023
16024       default:
16025         break;
16026     }
16027
16028     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16029         if(WhiteOnMove(forwardMostMove))
16030              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16031         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16032     }
16033
16034     tickStartTM = now;
16035     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16036       whiteTimeRemaining : blackTimeRemaining);
16037     StartClockTimer(intendedTickLength);
16038 }
16039
16040
16041 /* Stop both clocks */
16042 void
16043 StopClocks ()
16044 {
16045     long lastTickLength;
16046     TimeMark now;
16047
16048     if (!StopClockTimer()) return;
16049     if (!appData.clockMode) return;
16050
16051     GetTimeMark(&now);
16052
16053     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16054     if (WhiteOnMove(forwardMostMove)) {
16055         if(whiteNPS >= 0) lastTickLength = 0;
16056         whiteTimeRemaining -= lastTickLength;
16057         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16058     } else {
16059         if(blackNPS >= 0) lastTickLength = 0;
16060         blackTimeRemaining -= lastTickLength;
16061         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16062     }
16063     CheckFlags();
16064 }
16065
16066 /* Start clock of player on move.  Time may have been reset, so
16067    if clock is already running, stop and restart it. */
16068 void
16069 StartClocks ()
16070 {
16071     (void) StopClockTimer(); /* in case it was running already */
16072     DisplayBothClocks();
16073     if (CheckFlags()) return;
16074
16075     if (!appData.clockMode) return;
16076     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16077
16078     GetTimeMark(&tickStartTM);
16079     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16080       whiteTimeRemaining : blackTimeRemaining);
16081
16082    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16083     whiteNPS = blackNPS = -1;
16084     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16085        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16086         whiteNPS = first.nps;
16087     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16088        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16089         blackNPS = first.nps;
16090     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16091         whiteNPS = second.nps;
16092     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16093         blackNPS = second.nps;
16094     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16095
16096     StartClockTimer(intendedTickLength);
16097 }
16098
16099 char *
16100 TimeString (long ms)
16101 {
16102     long second, minute, hour, day;
16103     char *sign = "";
16104     static char buf[32];
16105
16106     if (ms > 0 && ms <= 9900) {
16107       /* convert milliseconds to tenths, rounding up */
16108       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16109
16110       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16111       return buf;
16112     }
16113
16114     /* convert milliseconds to seconds, rounding up */
16115     /* use floating point to avoid strangeness of integer division
16116        with negative dividends on many machines */
16117     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16118
16119     if (second < 0) {
16120         sign = "-";
16121         second = -second;
16122     }
16123
16124     day = second / (60 * 60 * 24);
16125     second = second % (60 * 60 * 24);
16126     hour = second / (60 * 60);
16127     second = second % (60 * 60);
16128     minute = second / 60;
16129     second = second % 60;
16130
16131     if (day > 0)
16132       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16133               sign, day, hour, minute, second);
16134     else if (hour > 0)
16135       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16136     else
16137       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16138
16139     return buf;
16140 }
16141
16142
16143 /*
16144  * This is necessary because some C libraries aren't ANSI C compliant yet.
16145  */
16146 char *
16147 StrStr (char *string, char *match)
16148 {
16149     int i, length;
16150
16151     length = strlen(match);
16152
16153     for (i = strlen(string) - length; i >= 0; i--, string++)
16154       if (!strncmp(match, string, length))
16155         return string;
16156
16157     return NULL;
16158 }
16159
16160 char *
16161 StrCaseStr (char *string, char *match)
16162 {
16163     int i, j, length;
16164
16165     length = strlen(match);
16166
16167     for (i = strlen(string) - length; i >= 0; i--, string++) {
16168         for (j = 0; j < length; j++) {
16169             if (ToLower(match[j]) != ToLower(string[j]))
16170               break;
16171         }
16172         if (j == length) return string;
16173     }
16174
16175     return NULL;
16176 }
16177
16178 #ifndef _amigados
16179 int
16180 StrCaseCmp (char *s1, char *s2)
16181 {
16182     char c1, c2;
16183
16184     for (;;) {
16185         c1 = ToLower(*s1++);
16186         c2 = ToLower(*s2++);
16187         if (c1 > c2) return 1;
16188         if (c1 < c2) return -1;
16189         if (c1 == NULLCHAR) return 0;
16190     }
16191 }
16192
16193
16194 int
16195 ToLower (int c)
16196 {
16197     return isupper(c) ? tolower(c) : c;
16198 }
16199
16200
16201 int
16202 ToUpper (int c)
16203 {
16204     return islower(c) ? toupper(c) : c;
16205 }
16206 #endif /* !_amigados    */
16207
16208 char *
16209 StrSave (char *s)
16210 {
16211   char *ret;
16212
16213   if ((ret = (char *) malloc(strlen(s) + 1)))
16214     {
16215       safeStrCpy(ret, s, strlen(s)+1);
16216     }
16217   return ret;
16218 }
16219
16220 char *
16221 StrSavePtr (char *s, char **savePtr)
16222 {
16223     if (*savePtr) {
16224         free(*savePtr);
16225     }
16226     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16227       safeStrCpy(*savePtr, s, strlen(s)+1);
16228     }
16229     return(*savePtr);
16230 }
16231
16232 char *
16233 PGNDate ()
16234 {
16235     time_t clock;
16236     struct tm *tm;
16237     char buf[MSG_SIZ];
16238
16239     clock = time((time_t *)NULL);
16240     tm = localtime(&clock);
16241     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16242             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16243     return StrSave(buf);
16244 }
16245
16246
16247 char *
16248 PositionToFEN (int move, char *overrideCastling)
16249 {
16250     int i, j, fromX, fromY, toX, toY;
16251     int whiteToPlay;
16252     char buf[MSG_SIZ];
16253     char *p, *q;
16254     int emptycount;
16255     ChessSquare piece;
16256
16257     whiteToPlay = (gameMode == EditPosition) ?
16258       !blackPlaysFirst : (move % 2 == 0);
16259     p = buf;
16260
16261     /* Piece placement data */
16262     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16263         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16264         emptycount = 0;
16265         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16266             if (boards[move][i][j] == EmptySquare) {
16267                 emptycount++;
16268             } else { ChessSquare piece = boards[move][i][j];
16269                 if (emptycount > 0) {
16270                     if(emptycount<10) /* [HGM] can be >= 10 */
16271                         *p++ = '0' + emptycount;
16272                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16273                     emptycount = 0;
16274                 }
16275                 if(PieceToChar(piece) == '+') {
16276                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16277                     *p++ = '+';
16278                     piece = (ChessSquare)(DEMOTED piece);
16279                 }
16280                 *p++ = PieceToChar(piece);
16281                 if(p[-1] == '~') {
16282                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16283                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16284                     *p++ = '~';
16285                 }
16286             }
16287         }
16288         if (emptycount > 0) {
16289             if(emptycount<10) /* [HGM] can be >= 10 */
16290                 *p++ = '0' + emptycount;
16291             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16292             emptycount = 0;
16293         }
16294         *p++ = '/';
16295     }
16296     *(p - 1) = ' ';
16297
16298     /* [HGM] print Crazyhouse or Shogi holdings */
16299     if( gameInfo.holdingsWidth ) {
16300         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16301         q = p;
16302         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16303             piece = boards[move][i][BOARD_WIDTH-1];
16304             if( piece != EmptySquare )
16305               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16306                   *p++ = PieceToChar(piece);
16307         }
16308         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16309             piece = boards[move][BOARD_HEIGHT-i-1][0];
16310             if( piece != EmptySquare )
16311               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16312                   *p++ = PieceToChar(piece);
16313         }
16314
16315         if( q == p ) *p++ = '-';
16316         *p++ = ']';
16317         *p++ = ' ';
16318     }
16319
16320     /* Active color */
16321     *p++ = whiteToPlay ? 'w' : 'b';
16322     *p++ = ' ';
16323
16324   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16325     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16326   } else {
16327   if(nrCastlingRights) {
16328      q = p;
16329      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16330        /* [HGM] write directly from rights */
16331            if(boards[move][CASTLING][2] != NoRights &&
16332               boards[move][CASTLING][0] != NoRights   )
16333                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16334            if(boards[move][CASTLING][2] != NoRights &&
16335               boards[move][CASTLING][1] != NoRights   )
16336                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16337            if(boards[move][CASTLING][5] != NoRights &&
16338               boards[move][CASTLING][3] != NoRights   )
16339                 *p++ = boards[move][CASTLING][3] + AAA;
16340            if(boards[move][CASTLING][5] != NoRights &&
16341               boards[move][CASTLING][4] != NoRights   )
16342                 *p++ = boards[move][CASTLING][4] + AAA;
16343      } else {
16344
16345         /* [HGM] write true castling rights */
16346         if( nrCastlingRights == 6 ) {
16347             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16348                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16349             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16350                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16351             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16352                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16353             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16354                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16355         }
16356      }
16357      if (q == p) *p++ = '-'; /* No castling rights */
16358      *p++ = ' ';
16359   }
16360
16361   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16362      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16363     /* En passant target square */
16364     if (move > backwardMostMove) {
16365         fromX = moveList[move - 1][0] - AAA;
16366         fromY = moveList[move - 1][1] - ONE;
16367         toX = moveList[move - 1][2] - AAA;
16368         toY = moveList[move - 1][3] - ONE;
16369         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16370             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16371             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16372             fromX == toX) {
16373             /* 2-square pawn move just happened */
16374             *p++ = toX + AAA;
16375             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16376         } else {
16377             *p++ = '-';
16378         }
16379     } else if(move == backwardMostMove) {
16380         // [HGM] perhaps we should always do it like this, and forget the above?
16381         if((signed char)boards[move][EP_STATUS] >= 0) {
16382             *p++ = boards[move][EP_STATUS] + AAA;
16383             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16384         } else {
16385             *p++ = '-';
16386         }
16387     } else {
16388         *p++ = '-';
16389     }
16390     *p++ = ' ';
16391   }
16392   }
16393
16394     /* [HGM] find reversible plies */
16395     {   int i = 0, j=move;
16396
16397         if (appData.debugMode) { int k;
16398             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16399             for(k=backwardMostMove; k<=forwardMostMove; k++)
16400                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16401
16402         }
16403
16404         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16405         if( j == backwardMostMove ) i += initialRulePlies;
16406         sprintf(p, "%d ", i);
16407         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16408     }
16409     /* Fullmove number */
16410     sprintf(p, "%d", (move / 2) + 1);
16411
16412     return StrSave(buf);
16413 }
16414
16415 Boolean
16416 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16417 {
16418     int i, j;
16419     char *p, c;
16420     int emptycount;
16421     ChessSquare piece;
16422
16423     p = fen;
16424
16425     /* [HGM] by default clear Crazyhouse holdings, if present */
16426     if(gameInfo.holdingsWidth) {
16427        for(i=0; i<BOARD_HEIGHT; i++) {
16428            board[i][0]             = EmptySquare; /* black holdings */
16429            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16430            board[i][1]             = (ChessSquare) 0; /* black counts */
16431            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16432        }
16433     }
16434
16435     /* Piece placement data */
16436     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16437         j = 0;
16438         for (;;) {
16439             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16440                 if (*p == '/') p++;
16441                 emptycount = gameInfo.boardWidth - j;
16442                 while (emptycount--)
16443                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16444                 break;
16445 #if(BOARD_FILES >= 10)
16446             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16447                 p++; emptycount=10;
16448                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16449                 while (emptycount--)
16450                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16451 #endif
16452             } else if (isdigit(*p)) {
16453                 emptycount = *p++ - '0';
16454                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16455                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16456                 while (emptycount--)
16457                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16458             } else if (*p == '+' || isalpha(*p)) {
16459                 if (j >= gameInfo.boardWidth) return FALSE;
16460                 if(*p=='+') {
16461                     piece = CharToPiece(*++p);
16462                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16463                     piece = (ChessSquare) (PROMOTED piece ); p++;
16464                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16465                 } else piece = CharToPiece(*p++);
16466
16467                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16468                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16469                     piece = (ChessSquare) (PROMOTED piece);
16470                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16471                     p++;
16472                 }
16473                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16474             } else {
16475                 return FALSE;
16476             }
16477         }
16478     }
16479     while (*p == '/' || *p == ' ') p++;
16480
16481     /* [HGM] look for Crazyhouse holdings here */
16482     while(*p==' ') p++;
16483     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16484         if(*p == '[') p++;
16485         if(*p == '-' ) p++; /* empty holdings */ else {
16486             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16487             /* if we would allow FEN reading to set board size, we would   */
16488             /* have to add holdings and shift the board read so far here   */
16489             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16490                 p++;
16491                 if((int) piece >= (int) BlackPawn ) {
16492                     i = (int)piece - (int)BlackPawn;
16493                     i = PieceToNumber((ChessSquare)i);
16494                     if( i >= gameInfo.holdingsSize ) return FALSE;
16495                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16496                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16497                 } else {
16498                     i = (int)piece - (int)WhitePawn;
16499                     i = PieceToNumber((ChessSquare)i);
16500                     if( i >= gameInfo.holdingsSize ) return FALSE;
16501                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16502                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16503                 }
16504             }
16505         }
16506         if(*p == ']') p++;
16507     }
16508
16509     while(*p == ' ') p++;
16510
16511     /* Active color */
16512     c = *p++;
16513     if(appData.colorNickNames) {
16514       if( c == appData.colorNickNames[0] ) c = 'w'; else
16515       if( c == appData.colorNickNames[1] ) c = 'b';
16516     }
16517     switch (c) {
16518       case 'w':
16519         *blackPlaysFirst = FALSE;
16520         break;
16521       case 'b':
16522         *blackPlaysFirst = TRUE;
16523         break;
16524       default:
16525         return FALSE;
16526     }
16527
16528     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16529     /* return the extra info in global variiables             */
16530
16531     /* set defaults in case FEN is incomplete */
16532     board[EP_STATUS] = EP_UNKNOWN;
16533     for(i=0; i<nrCastlingRights; i++ ) {
16534         board[CASTLING][i] =
16535             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16536     }   /* assume possible unless obviously impossible */
16537     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16538     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16539     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16540                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16541     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16542     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16543     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16544                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16545     FENrulePlies = 0;
16546
16547     while(*p==' ') p++;
16548     if(nrCastlingRights) {
16549       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16550           /* castling indicator present, so default becomes no castlings */
16551           for(i=0; i<nrCastlingRights; i++ ) {
16552                  board[CASTLING][i] = NoRights;
16553           }
16554       }
16555       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16556              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16557              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16558              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16559         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16560
16561         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16562             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16563             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16564         }
16565         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16566             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16567         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16568                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16569         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16570                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16571         switch(c) {
16572           case'K':
16573               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16574               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16575               board[CASTLING][2] = whiteKingFile;
16576               break;
16577           case'Q':
16578               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16579               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16580               board[CASTLING][2] = whiteKingFile;
16581               break;
16582           case'k':
16583               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16584               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16585               board[CASTLING][5] = blackKingFile;
16586               break;
16587           case'q':
16588               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16589               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16590               board[CASTLING][5] = blackKingFile;
16591           case '-':
16592               break;
16593           default: /* FRC castlings */
16594               if(c >= 'a') { /* black rights */
16595                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16596                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16597                   if(i == BOARD_RGHT) break;
16598                   board[CASTLING][5] = i;
16599                   c -= AAA;
16600                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16601                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16602                   if(c > i)
16603                       board[CASTLING][3] = c;
16604                   else
16605                       board[CASTLING][4] = c;
16606               } else { /* white rights */
16607                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16608                     if(board[0][i] == WhiteKing) break;
16609                   if(i == BOARD_RGHT) break;
16610                   board[CASTLING][2] = i;
16611                   c -= AAA - 'a' + 'A';
16612                   if(board[0][c] >= WhiteKing) break;
16613                   if(c > i)
16614                       board[CASTLING][0] = c;
16615                   else
16616                       board[CASTLING][1] = c;
16617               }
16618         }
16619       }
16620       for(i=0; i<nrCastlingRights; i++)
16621         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16622     if (appData.debugMode) {
16623         fprintf(debugFP, "FEN castling rights:");
16624         for(i=0; i<nrCastlingRights; i++)
16625         fprintf(debugFP, " %d", board[CASTLING][i]);
16626         fprintf(debugFP, "\n");
16627     }
16628
16629       while(*p==' ') p++;
16630     }
16631
16632     /* read e.p. field in games that know e.p. capture */
16633     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16634        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16635       if(*p=='-') {
16636         p++; board[EP_STATUS] = EP_NONE;
16637       } else {
16638          char c = *p++ - AAA;
16639
16640          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16641          if(*p >= '0' && *p <='9') p++;
16642          board[EP_STATUS] = c;
16643       }
16644     }
16645
16646
16647     if(sscanf(p, "%d", &i) == 1) {
16648         FENrulePlies = i; /* 50-move ply counter */
16649         /* (The move number is still ignored)    */
16650     }
16651
16652     return TRUE;
16653 }
16654
16655 void
16656 EditPositionPasteFEN (char *fen)
16657 {
16658   if (fen != NULL) {
16659     Board initial_position;
16660
16661     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16662       DisplayError(_("Bad FEN position in clipboard"), 0);
16663       return ;
16664     } else {
16665       int savedBlackPlaysFirst = blackPlaysFirst;
16666       EditPositionEvent();
16667       blackPlaysFirst = savedBlackPlaysFirst;
16668       CopyBoard(boards[0], initial_position);
16669       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16670       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16671       DisplayBothClocks();
16672       DrawPosition(FALSE, boards[currentMove]);
16673     }
16674   }
16675 }
16676
16677 static char cseq[12] = "\\   ";
16678
16679 Boolean
16680 set_cont_sequence (char *new_seq)
16681 {
16682     int len;
16683     Boolean ret;
16684
16685     // handle bad attempts to set the sequence
16686         if (!new_seq)
16687                 return 0; // acceptable error - no debug
16688
16689     len = strlen(new_seq);
16690     ret = (len > 0) && (len < sizeof(cseq));
16691     if (ret)
16692       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16693     else if (appData.debugMode)
16694       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16695     return ret;
16696 }
16697
16698 /*
16699     reformat a source message so words don't cross the width boundary.  internal
16700     newlines are not removed.  returns the wrapped size (no null character unless
16701     included in source message).  If dest is NULL, only calculate the size required
16702     for the dest buffer.  lp argument indicats line position upon entry, and it's
16703     passed back upon exit.
16704 */
16705 int
16706 wrap (char *dest, char *src, int count, int width, int *lp)
16707 {
16708     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16709
16710     cseq_len = strlen(cseq);
16711     old_line = line = *lp;
16712     ansi = len = clen = 0;
16713
16714     for (i=0; i < count; i++)
16715     {
16716         if (src[i] == '\033')
16717             ansi = 1;
16718
16719         // if we hit the width, back up
16720         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16721         {
16722             // store i & len in case the word is too long
16723             old_i = i, old_len = len;
16724
16725             // find the end of the last word
16726             while (i && src[i] != ' ' && src[i] != '\n')
16727             {
16728                 i--;
16729                 len--;
16730             }
16731
16732             // word too long?  restore i & len before splitting it
16733             if ((old_i-i+clen) >= width)
16734             {
16735                 i = old_i;
16736                 len = old_len;
16737             }
16738
16739             // extra space?
16740             if (i && src[i-1] == ' ')
16741                 len--;
16742
16743             if (src[i] != ' ' && src[i] != '\n')
16744             {
16745                 i--;
16746                 if (len)
16747                     len--;
16748             }
16749
16750             // now append the newline and continuation sequence
16751             if (dest)
16752                 dest[len] = '\n';
16753             len++;
16754             if (dest)
16755                 strncpy(dest+len, cseq, cseq_len);
16756             len += cseq_len;
16757             line = cseq_len;
16758             clen = cseq_len;
16759             continue;
16760         }
16761
16762         if (dest)
16763             dest[len] = src[i];
16764         len++;
16765         if (!ansi)
16766             line++;
16767         if (src[i] == '\n')
16768             line = 0;
16769         if (src[i] == 'm')
16770             ansi = 0;
16771     }
16772     if (dest && appData.debugMode)
16773     {
16774         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16775             count, width, line, len, *lp);
16776         show_bytes(debugFP, src, count);
16777         fprintf(debugFP, "\ndest: ");
16778         show_bytes(debugFP, dest, len);
16779         fprintf(debugFP, "\n");
16780     }
16781     *lp = dest ? line : old_line;
16782
16783     return len;
16784 }
16785
16786 // [HGM] vari: routines for shelving variations
16787 Boolean modeRestore = FALSE;
16788
16789 void
16790 PushInner (int firstMove, int lastMove)
16791 {
16792         int i, j, nrMoves = lastMove - firstMove;
16793
16794         // push current tail of game on stack
16795         savedResult[storedGames] = gameInfo.result;
16796         savedDetails[storedGames] = gameInfo.resultDetails;
16797         gameInfo.resultDetails = NULL;
16798         savedFirst[storedGames] = firstMove;
16799         savedLast [storedGames] = lastMove;
16800         savedFramePtr[storedGames] = framePtr;
16801         framePtr -= nrMoves; // reserve space for the boards
16802         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16803             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16804             for(j=0; j<MOVE_LEN; j++)
16805                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16806             for(j=0; j<2*MOVE_LEN; j++)
16807                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16808             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16809             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16810             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16811             pvInfoList[firstMove+i-1].depth = 0;
16812             commentList[framePtr+i] = commentList[firstMove+i];
16813             commentList[firstMove+i] = NULL;
16814         }
16815
16816         storedGames++;
16817         forwardMostMove = firstMove; // truncate game so we can start variation
16818 }
16819
16820 void
16821 PushTail (int firstMove, int lastMove)
16822 {
16823         if(appData.icsActive) { // only in local mode
16824                 forwardMostMove = currentMove; // mimic old ICS behavior
16825                 return;
16826         }
16827         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16828
16829         PushInner(firstMove, lastMove);
16830         if(storedGames == 1) GreyRevert(FALSE);
16831         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16832 }
16833
16834 void
16835 PopInner (Boolean annotate)
16836 {
16837         int i, j, nrMoves;
16838         char buf[8000], moveBuf[20];
16839
16840         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16841         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16842         nrMoves = savedLast[storedGames] - currentMove;
16843         if(annotate) {
16844                 int cnt = 10;
16845                 if(!WhiteOnMove(currentMove))
16846                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16847                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16848                 for(i=currentMove; i<forwardMostMove; i++) {
16849                         if(WhiteOnMove(i))
16850                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16851                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16852                         strcat(buf, moveBuf);
16853                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16854                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16855                 }
16856                 strcat(buf, ")");
16857         }
16858         for(i=1; i<=nrMoves; i++) { // copy last variation back
16859             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16860             for(j=0; j<MOVE_LEN; j++)
16861                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16862             for(j=0; j<2*MOVE_LEN; j++)
16863                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16864             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16865             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16866             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16867             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16868             commentList[currentMove+i] = commentList[framePtr+i];
16869             commentList[framePtr+i] = NULL;
16870         }
16871         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16872         framePtr = savedFramePtr[storedGames];
16873         gameInfo.result = savedResult[storedGames];
16874         if(gameInfo.resultDetails != NULL) {
16875             free(gameInfo.resultDetails);
16876       }
16877         gameInfo.resultDetails = savedDetails[storedGames];
16878         forwardMostMove = currentMove + nrMoves;
16879 }
16880
16881 Boolean
16882 PopTail (Boolean annotate)
16883 {
16884         if(appData.icsActive) return FALSE; // only in local mode
16885         if(!storedGames) return FALSE; // sanity
16886         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16887
16888         PopInner(annotate);
16889         if(currentMove < forwardMostMove) ForwardEvent(); else
16890         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16891
16892         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16893         return TRUE;
16894 }
16895
16896 void
16897 CleanupTail ()
16898 {       // remove all shelved variations
16899         int i;
16900         for(i=0; i<storedGames; i++) {
16901             if(savedDetails[i])
16902                 free(savedDetails[i]);
16903             savedDetails[i] = NULL;
16904         }
16905         for(i=framePtr; i<MAX_MOVES; i++) {
16906                 if(commentList[i]) free(commentList[i]);
16907                 commentList[i] = NULL;
16908         }
16909         framePtr = MAX_MOVES-1;
16910         storedGames = 0;
16911 }
16912
16913 void
16914 LoadVariation (int index, char *text)
16915 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16916         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16917         int level = 0, move;
16918
16919         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16920         // first find outermost bracketing variation
16921         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16922             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16923                 if(*p == '{') wait = '}'; else
16924                 if(*p == '[') wait = ']'; else
16925                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16926                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16927             }
16928             if(*p == wait) wait = NULLCHAR; // closing ]} found
16929             p++;
16930         }
16931         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16932         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16933         end[1] = NULLCHAR; // clip off comment beyond variation
16934         ToNrEvent(currentMove-1);
16935         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16936         // kludge: use ParsePV() to append variation to game
16937         move = currentMove;
16938         ParsePV(start, TRUE, TRUE);
16939         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16940         ClearPremoveHighlights();
16941         CommentPopDown();
16942         ToNrEvent(currentMove+1);
16943 }
16944