Allow setting of holdings with edit command
[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 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(str)
647   char *str;
648 {
649   while(*str && !isdigit(*str)) ++str;
650   if (!*str)
651     return 0;   /* One of the special "no rating" cases */
652   else
653     return atoi(str);
654 }
655
656 void
657 ClearProgramStats()
658 {
659     /* Init programStats */
660     programStats.movelist[0] = 0;
661     programStats.depth = 0;
662     programStats.nr_moves = 0;
663     programStats.moves_left = 0;
664     programStats.nodes = 0;
665     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
666     programStats.score = 0;
667     programStats.got_only_move = 0;
668     programStats.got_fail = 0;
669     programStats.line_is_book = 0;
670 }
671
672 void
673 CommonEngineInit()
674 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
675     if (appData.firstPlaysBlack) {
676         first.twoMachinesColor = "black\n";
677         second.twoMachinesColor = "white\n";
678     } else {
679         first.twoMachinesColor = "white\n";
680         second.twoMachinesColor = "black\n";
681     }
682
683     first.other = &second;
684     second.other = &first;
685
686     { float norm = 1;
687         if(appData.timeOddsMode) {
688             norm = appData.timeOdds[0];
689             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
690         }
691         first.timeOdds  = appData.timeOdds[0]/norm;
692         second.timeOdds = appData.timeOdds[1]/norm;
693     }
694
695     if(programVersion) free(programVersion);
696     if (appData.noChessProgram) {
697         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
698         sprintf(programVersion, "%s", PACKAGE_STRING);
699     } else {
700       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
701       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
702       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
703     }
704 }
705
706 void
707 UnloadEngine(ChessProgramState *cps)
708 {
709         /* Kill off first chess program */
710         if (cps->isr != NULL)
711           RemoveInputSource(cps->isr);
712         cps->isr = NULL;
713
714         if (cps->pr != NoProc) {
715             ExitAnalyzeMode();
716             DoSleep( appData.delayBeforeQuit );
717             SendToProgram("quit\n", cps);
718             DoSleep( appData.delayAfterQuit );
719             DestroyChildProcess(cps->pr, cps->useSigterm);
720         }
721         cps->pr = NoProc;
722         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
723 }
724
725 void
726 ClearOptions(ChessProgramState *cps)
727 {
728     int i;
729     cps->nrOptions = cps->comboCnt = 0;
730     for(i=0; i<MAX_OPTIONS; i++) {
731         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
732         cps->option[i].textValue = 0;
733     }
734 }
735
736 char *engineNames[] = {
737 "first",
738 "second"
739 };
740
741 void
742 InitEngine(ChessProgramState *cps, int n)
743 {   // [HGM] all engine initialiation put in a function that does one engine
744
745     ClearOptions(cps);
746
747     cps->which = engineNames[n];
748     cps->maybeThinking = FALSE;
749     cps->pr = NoProc;
750     cps->isr = NULL;
751     cps->sendTime = 2;
752     cps->sendDrawOffers = 1;
753
754     cps->program = appData.chessProgram[n];
755     cps->host = appData.host[n];
756     cps->dir = appData.directory[n];
757     cps->initString = appData.engInitString[n];
758     cps->computerString = appData.computerString[n];
759     cps->useSigint  = TRUE;
760     cps->useSigterm = TRUE;
761     cps->reuse = appData.reuse[n];
762     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
763     cps->useSetboard = FALSE;
764     cps->useSAN = FALSE;
765     cps->usePing = FALSE;
766     cps->lastPing = 0;
767     cps->lastPong = 0;
768     cps->usePlayother = FALSE;
769     cps->useColors = TRUE;
770     cps->useUsermove = FALSE;
771     cps->sendICS = FALSE;
772     cps->sendName = appData.icsActive;
773     cps->sdKludge = FALSE;
774     cps->stKludge = FALSE;
775     TidyProgramName(cps->program, cps->host, cps->tidy);
776     cps->matchWins = 0;
777     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
778     cps->analysisSupport = 2; /* detect */
779     cps->analyzing = FALSE;
780     cps->initDone = FALSE;
781
782     /* New features added by Tord: */
783     cps->useFEN960 = FALSE;
784     cps->useOOCastle = TRUE;
785     /* End of new features added by Tord. */
786     cps->fenOverride  = appData.fenOverride[n];
787
788     /* [HGM] time odds: set factor for each machine */
789     cps->timeOdds  = appData.timeOdds[n];
790
791     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
792     cps->accumulateTC = appData.accumulateTC[n];
793     cps->maxNrOfSessions = 1;
794
795     /* [HGM] debug */
796     cps->debug = FALSE;
797
798     cps->supportsNPS = UNKNOWN;
799     cps->memSize = FALSE;
800     cps->maxCores = FALSE;
801     cps->egtFormats[0] = NULLCHAR;
802
803     /* [HGM] options */
804     cps->optionSettings  = appData.engOptions[n];
805
806     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
807     cps->isUCI = appData.isUCI[n]; /* [AS] */
808     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
809
810     if (appData.protocolVersion[n] > PROTOVER
811         || appData.protocolVersion[n] < 1)
812       {
813         char buf[MSG_SIZ];
814         int len;
815
816         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
817                        appData.protocolVersion[n]);
818         if( (len >= MSG_SIZ) && appData.debugMode )
819           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
820
821         DisplayFatalError(buf, 0, 2);
822       }
823     else
824       {
825         cps->protocolVersion = appData.protocolVersion[n];
826       }
827
828     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
829     ParseFeatures(appData.featureDefaults, cps);
830 }
831
832 ChessProgramState *savCps;
833
834 void
835 LoadEngine()
836 {
837     int i;
838     if(WaitForEngine(savCps, LoadEngine)) return;
839     CommonEngineInit(); // recalculate time odds
840     if(gameInfo.variant != StringToVariant(appData.variant)) {
841         // we changed variant when loading the engine; this forces us to reset
842         Reset(TRUE, savCps != &first);
843         EditGameEvent(); // for consistency with other path, as Reset changes mode
844     }
845     InitChessProgram(savCps, FALSE);
846     SendToProgram("force\n", savCps);
847     DisplayMessage("", "");
848     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
849     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
850     ThawUI();
851     SetGNUMode();
852 }
853
854 void
855 ReplaceEngine(ChessProgramState *cps, int n)
856 {
857     EditGameEvent();
858     UnloadEngine(cps);
859     appData.noChessProgram = FALSE;
860     appData.clockMode = TRUE;
861     InitEngine(cps, n);
862     UpdateLogos(TRUE);
863     if(n) return; // only startup first engine immediately; second can wait
864     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
865     LoadEngine();
866 }
867
868 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
869 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
870
871 static char resetOptions[] = 
872         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
873         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
874         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
875
876 void
877 Load(ChessProgramState *cps, int i)
878 {
879     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
880     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
881         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
882         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
883         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
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 NextIntegerFromString( char ** str, long * value )
1130 {
1131     int result = -1;
1132     char * s = *str;
1133
1134     while( *s == ' ' || *s == '\t' ) {
1135         s++;
1136     }
1137
1138     *value = 0;
1139
1140     if( *s >= '0' && *s <= '9' ) {
1141         while( *s >= '0' && *s <= '9' ) {
1142             *value = *value * 10 + (*s - '0');
1143             s++;
1144         }
1145
1146         result = 0;
1147     }
1148
1149     *str = s;
1150
1151     return result;
1152 }
1153
1154 int NextTimeControlFromString( char ** str, long * value )
1155 {
1156     long temp;
1157     int result = NextIntegerFromString( str, &temp );
1158
1159     if( result == 0 ) {
1160         *value = temp * 60; /* Minutes */
1161         if( **str == ':' ) {
1162             (*str)++;
1163             result = NextIntegerFromString( str, &temp );
1164             *value += temp; /* Seconds */
1165         }
1166     }
1167
1168     return result;
1169 }
1170
1171 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1172 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1173     int result = -1, type = 0; long temp, temp2;
1174
1175     if(**str != ':') return -1; // old params remain in force!
1176     (*str)++;
1177     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1178     if( NextIntegerFromString( str, &temp ) ) return -1;
1179     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1180
1181     if(**str != '/') {
1182         /* time only: incremental or sudden-death time control */
1183         if(**str == '+') { /* increment follows; read it */
1184             (*str)++;
1185             if(**str == '!') type = *(*str)++; // Bronstein TC
1186             if(result = NextIntegerFromString( str, &temp2)) return -1;
1187             *inc = temp2 * 1000;
1188             if(**str == '.') { // read fraction of increment
1189                 char *start = ++(*str);
1190                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1191                 temp2 *= 1000;
1192                 while(start++ < *str) temp2 /= 10;
1193                 *inc += temp2;
1194             }
1195         } else *inc = 0;
1196         *moves = 0; *tc = temp * 1000; *incType = type;
1197         return 0;
1198     }
1199
1200     (*str)++; /* classical time control */
1201     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1202
1203     if(result == 0) {
1204         *moves = temp;
1205         *tc    = temp2 * 1000;
1206         *inc   = 0;
1207         *incType = type;
1208     }
1209     return result;
1210 }
1211
1212 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1213 {   /* [HGM] get time to add from the multi-session time-control string */
1214     int incType, moves=1; /* kludge to force reading of first session */
1215     long time, increment;
1216     char *s = tcString;
1217
1218     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1219     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1220     do {
1221         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1222         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1223         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1224         if(movenr == -1) return time;    /* last move before new session     */
1225         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1226         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1227         if(!moves) return increment;     /* current session is incremental   */
1228         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1229     } while(movenr >= -1);               /* try again for next session       */
1230
1231     return 0; // no new time quota on this move
1232 }
1233
1234 int
1235 ParseTimeControl(tc, ti, mps)
1236      char *tc;
1237      float ti;
1238      int mps;
1239 {
1240   long tc1;
1241   long tc2;
1242   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1243   int min, sec=0;
1244
1245   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1246   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1247       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1248   if(ti > 0) {
1249
1250     if(mps)
1251       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1252     else 
1253       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1254   } else {
1255     if(mps)
1256       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1257     else 
1258       snprintf(buf, MSG_SIZ, ":%s", mytc);
1259   }
1260   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1261   
1262   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1263     return FALSE;
1264   }
1265
1266   if( *tc == '/' ) {
1267     /* Parse second time control */
1268     tc++;
1269
1270     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1271       return FALSE;
1272     }
1273
1274     if( tc2 == 0 ) {
1275       return FALSE;
1276     }
1277
1278     timeControl_2 = tc2 * 1000;
1279   }
1280   else {
1281     timeControl_2 = 0;
1282   }
1283
1284   if( tc1 == 0 ) {
1285     return FALSE;
1286   }
1287
1288   timeControl = tc1 * 1000;
1289
1290   if (ti >= 0) {
1291     timeIncrement = ti * 1000;  /* convert to ms */
1292     movesPerSession = 0;
1293   } else {
1294     timeIncrement = 0;
1295     movesPerSession = mps;
1296   }
1297   return TRUE;
1298 }
1299
1300 void
1301 InitBackEnd2()
1302 {
1303     if (appData.debugMode) {
1304         fprintf(debugFP, "%s\n", programVersion);
1305     }
1306
1307     set_cont_sequence(appData.wrapContSeq);
1308     if (appData.matchGames > 0) {
1309         appData.matchMode = TRUE;
1310     } else if (appData.matchMode) {
1311         appData.matchGames = 1;
1312     }
1313     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1314         appData.matchGames = appData.sameColorGames;
1315     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1316         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1317         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1318     }
1319     Reset(TRUE, FALSE);
1320     if (appData.noChessProgram || first.protocolVersion == 1) {
1321       InitBackEnd3();
1322     } else {
1323       /* kludge: allow timeout for initial "feature" commands */
1324       FreezeUI();
1325       DisplayMessage("", _("Starting chess program"));
1326       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1327     }
1328 }
1329
1330 int
1331 CalculateIndex(int index, int gameNr)
1332 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1333     int res;
1334     if(index > 0) return index; // fixed nmber
1335     if(index == 0) return 1;
1336     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1337     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1338     return res;
1339 }
1340
1341 int
1342 LoadGameOrPosition(int gameNr)
1343 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1344     if (*appData.loadGameFile != NULLCHAR) {
1345         if (!LoadGameFromFile(appData.loadGameFile,
1346                 CalculateIndex(appData.loadGameIndex, gameNr),
1347                               appData.loadGameFile, FALSE)) {
1348             DisplayFatalError(_("Bad game file"), 0, 1);
1349             return 0;
1350         }
1351     } else if (*appData.loadPositionFile != NULLCHAR) {
1352         if (!LoadPositionFromFile(appData.loadPositionFile,
1353                 CalculateIndex(appData.loadPositionIndex, gameNr),
1354                                   appData.loadPositionFile)) {
1355             DisplayFatalError(_("Bad position file"), 0, 1);
1356             return 0;
1357         }
1358     }
1359     return 1;
1360 }
1361
1362 void
1363 ReserveGame(int gameNr, char resChar)
1364 {
1365     FILE *tf = fopen(appData.tourneyFile, "r+");
1366     char *p, *q, c, buf[MSG_SIZ];
1367     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1368     safeStrCpy(buf, lastMsg, MSG_SIZ);
1369     DisplayMessage(_("Pick new game"), "");
1370     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1371     ParseArgsFromFile(tf);
1372     p = q = appData.results;
1373     if(appData.debugMode) {
1374       char *r = appData.participants;
1375       fprintf(debugFP, "results = '%s'\n", p);
1376       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1377       fprintf(debugFP, "\n");
1378     }
1379     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1380     nextGame = q - p;
1381     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1382     safeStrCpy(q, p, strlen(p) + 2);
1383     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1384     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1385     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1386         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1387         q[nextGame] = '*';
1388     }
1389     fseek(tf, -(strlen(p)+4), SEEK_END);
1390     c = fgetc(tf);
1391     if(c != '"') // depending on DOS or Unix line endings we can be one off
1392          fseek(tf, -(strlen(p)+2), SEEK_END);
1393     else fseek(tf, -(strlen(p)+3), SEEK_END);
1394     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1395     DisplayMessage(buf, "");
1396     free(p); appData.results = q;
1397     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1398        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1399         UnloadEngine(&first);  // next game belongs to other pairing;
1400         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1401     }
1402 }
1403
1404 void
1405 MatchEvent(int mode)
1406 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1407         int dummy;
1408         if(matchMode) { // already in match mode: switch it off
1409             abortMatch = TRUE;
1410             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1411             return;
1412         }
1413 //      if(gameMode != BeginningOfGame) {
1414 //          DisplayError(_("You can only start a match from the initial position."), 0);
1415 //          return;
1416 //      }
1417         abortMatch = FALSE;
1418         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1419         /* Set up machine vs. machine match */
1420         nextGame = 0;
1421         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1422         if(appData.tourneyFile[0]) {
1423             ReserveGame(-1, 0);
1424             if(nextGame > appData.matchGames) {
1425                 char buf[MSG_SIZ];
1426                 if(strchr(appData.results, '*') == NULL) {
1427                     FILE *f;
1428                     appData.tourneyCycles++;
1429                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1430                         fclose(f);
1431                         NextTourneyGame(-1, &dummy);
1432                         ReserveGame(-1, 0);
1433                         if(nextGame <= appData.matchGames) {
1434                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1435                             matchMode = mode;
1436                             ScheduleDelayedEvent(NextMatchGame, 10000);
1437                             return;
1438                         }
1439                     }
1440                 }
1441                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1442                 DisplayError(buf, 0);
1443                 appData.tourneyFile[0] = 0;
1444                 return;
1445             }
1446         } else
1447         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1448             DisplayFatalError(_("Can't have a match with no chess programs"),
1449                               0, 2);
1450             return;
1451         }
1452         matchMode = mode;
1453         matchGame = roundNr = 1;
1454         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1455         NextMatchGame();
1456 }
1457
1458 void
1459 InitBackEnd3 P((void))
1460 {
1461     GameMode initialMode;
1462     char buf[MSG_SIZ];
1463     int err, len;
1464
1465     InitChessProgram(&first, startedFromSetupPosition);
1466
1467     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1468         free(programVersion);
1469         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1470         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1471     }
1472
1473     if (appData.icsActive) {
1474 #ifdef WIN32
1475         /* [DM] Make a console window if needed [HGM] merged ifs */
1476         ConsoleCreate();
1477 #endif
1478         err = establish();
1479         if (err != 0)
1480           {
1481             if (*appData.icsCommPort != NULLCHAR)
1482               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1483                              appData.icsCommPort);
1484             else
1485               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1486                         appData.icsHost, appData.icsPort);
1487
1488             if( (len >= MSG_SIZ) && appData.debugMode )
1489               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1490
1491             DisplayFatalError(buf, err, 1);
1492             return;
1493         }
1494         SetICSMode();
1495         telnetISR =
1496           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1497         fromUserISR =
1498           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1499         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1500             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1501     } else if (appData.noChessProgram) {
1502         SetNCPMode();
1503     } else {
1504         SetGNUMode();
1505     }
1506
1507     if (*appData.cmailGameName != NULLCHAR) {
1508         SetCmailMode();
1509         OpenLoopback(&cmailPR);
1510         cmailISR =
1511           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1512     }
1513
1514     ThawUI();
1515     DisplayMessage("", "");
1516     if (StrCaseCmp(appData.initialMode, "") == 0) {
1517       initialMode = BeginningOfGame;
1518       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1519         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1520         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1521         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1522         ModeHighlight();
1523       }
1524     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1525       initialMode = TwoMachinesPlay;
1526     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1527       initialMode = AnalyzeFile;
1528     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1529       initialMode = AnalyzeMode;
1530     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1531       initialMode = MachinePlaysWhite;
1532     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1533       initialMode = MachinePlaysBlack;
1534     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1535       initialMode = EditGame;
1536     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1537       initialMode = EditPosition;
1538     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1539       initialMode = Training;
1540     } else {
1541       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1542       if( (len >= MSG_SIZ) && appData.debugMode )
1543         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1544
1545       DisplayFatalError(buf, 0, 2);
1546       return;
1547     }
1548
1549     if (appData.matchMode) {
1550         if(appData.tourneyFile[0]) { // start tourney from command line
1551             FILE *f;
1552             if(f = fopen(appData.tourneyFile, "r")) {
1553                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1554                 fclose(f);
1555                 appData.clockMode = TRUE;
1556                 SetGNUMode();
1557             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1558         }
1559         MatchEvent(TRUE);
1560     } else if (*appData.cmailGameName != NULLCHAR) {
1561         /* Set up cmail mode */
1562         ReloadCmailMsgEvent(TRUE);
1563     } else {
1564         /* Set up other modes */
1565         if (initialMode == AnalyzeFile) {
1566           if (*appData.loadGameFile == NULLCHAR) {
1567             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1568             return;
1569           }
1570         }
1571         if (*appData.loadGameFile != NULLCHAR) {
1572             (void) LoadGameFromFile(appData.loadGameFile,
1573                                     appData.loadGameIndex,
1574                                     appData.loadGameFile, TRUE);
1575         } else if (*appData.loadPositionFile != NULLCHAR) {
1576             (void) LoadPositionFromFile(appData.loadPositionFile,
1577                                         appData.loadPositionIndex,
1578                                         appData.loadPositionFile);
1579             /* [HGM] try to make self-starting even after FEN load */
1580             /* to allow automatic setup of fairy variants with wtm */
1581             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1582                 gameMode = BeginningOfGame;
1583                 setboardSpoiledMachineBlack = 1;
1584             }
1585             /* [HGM] loadPos: make that every new game uses the setup */
1586             /* from file as long as we do not switch variant          */
1587             if(!blackPlaysFirst) {
1588                 startedFromPositionFile = TRUE;
1589                 CopyBoard(filePosition, boards[0]);
1590             }
1591         }
1592         if (initialMode == AnalyzeMode) {
1593           if (appData.noChessProgram) {
1594             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1595             return;
1596           }
1597           if (appData.icsActive) {
1598             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1599             return;
1600           }
1601           AnalyzeModeEvent();
1602         } else if (initialMode == AnalyzeFile) {
1603           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1604           ShowThinkingEvent();
1605           AnalyzeFileEvent();
1606           AnalysisPeriodicEvent(1);
1607         } else if (initialMode == MachinePlaysWhite) {
1608           if (appData.noChessProgram) {
1609             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1610                               0, 2);
1611             return;
1612           }
1613           if (appData.icsActive) {
1614             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1615                               0, 2);
1616             return;
1617           }
1618           MachineWhiteEvent();
1619         } else if (initialMode == MachinePlaysBlack) {
1620           if (appData.noChessProgram) {
1621             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1622                               0, 2);
1623             return;
1624           }
1625           if (appData.icsActive) {
1626             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1627                               0, 2);
1628             return;
1629           }
1630           MachineBlackEvent();
1631         } else if (initialMode == TwoMachinesPlay) {
1632           if (appData.noChessProgram) {
1633             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1634                               0, 2);
1635             return;
1636           }
1637           if (appData.icsActive) {
1638             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1639                               0, 2);
1640             return;
1641           }
1642           TwoMachinesEvent();
1643         } else if (initialMode == EditGame) {
1644           EditGameEvent();
1645         } else if (initialMode == EditPosition) {
1646           EditPositionEvent();
1647         } else if (initialMode == Training) {
1648           if (*appData.loadGameFile == NULLCHAR) {
1649             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1650             return;
1651           }
1652           TrainingEvent();
1653         }
1654     }
1655 }
1656
1657 void
1658 HistorySet( char movelist[][2*MOVE_LEN], int first, int last, int current )
1659 {
1660     DisplayBook(current+1);
1661
1662     MoveHistorySet( movelist, first, last, current, pvInfoList );
1663
1664     EvalGraphSet( first, last, current, pvInfoList );
1665
1666     MakeEngineOutputTitle();
1667 }
1668
1669 /*
1670  * Establish will establish a contact to a remote host.port.
1671  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1672  *  used to talk to the host.
1673  * Returns 0 if okay, error code if not.
1674  */
1675 int
1676 establish()
1677 {
1678     char buf[MSG_SIZ];
1679
1680     if (*appData.icsCommPort != NULLCHAR) {
1681         /* Talk to the host through a serial comm port */
1682         return OpenCommPort(appData.icsCommPort, &icsPR);
1683
1684     } else if (*appData.gateway != NULLCHAR) {
1685         if (*appData.remoteShell == NULLCHAR) {
1686             /* Use the rcmd protocol to run telnet program on a gateway host */
1687             snprintf(buf, sizeof(buf), "%s %s %s",
1688                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1689             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1690
1691         } else {
1692             /* Use the rsh program to run telnet program on a gateway host */
1693             if (*appData.remoteUser == NULLCHAR) {
1694                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1695                         appData.gateway, appData.telnetProgram,
1696                         appData.icsHost, appData.icsPort);
1697             } else {
1698                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1699                         appData.remoteShell, appData.gateway,
1700                         appData.remoteUser, appData.telnetProgram,
1701                         appData.icsHost, appData.icsPort);
1702             }
1703             return StartChildProcess(buf, "", &icsPR);
1704
1705         }
1706     } else if (appData.useTelnet) {
1707         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1708
1709     } else {
1710         /* TCP socket interface differs somewhat between
1711            Unix and NT; handle details in the front end.
1712            */
1713         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1714     }
1715 }
1716
1717 void EscapeExpand(char *p, char *q)
1718 {       // [HGM] initstring: routine to shape up string arguments
1719         while(*p++ = *q++) if(p[-1] == '\\')
1720             switch(*q++) {
1721                 case 'n': p[-1] = '\n'; break;
1722                 case 'r': p[-1] = '\r'; break;
1723                 case 't': p[-1] = '\t'; break;
1724                 case '\\': p[-1] = '\\'; break;
1725                 case 0: *p = 0; return;
1726                 default: p[-1] = q[-1]; break;
1727             }
1728 }
1729
1730 void
1731 show_bytes(fp, buf, count)
1732      FILE *fp;
1733      char *buf;
1734      int count;
1735 {
1736     while (count--) {
1737         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1738             fprintf(fp, "\\%03o", *buf & 0xff);
1739         } else {
1740             putc(*buf, fp);
1741         }
1742         buf++;
1743     }
1744     fflush(fp);
1745 }
1746
1747 /* Returns an errno value */
1748 int
1749 OutputMaybeTelnet(pr, message, count, outError)
1750      ProcRef pr;
1751      char *message;
1752      int count;
1753      int *outError;
1754 {
1755     char buf[8192], *p, *q, *buflim;
1756     int left, newcount, outcount;
1757
1758     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1759         *appData.gateway != NULLCHAR) {
1760         if (appData.debugMode) {
1761             fprintf(debugFP, ">ICS: ");
1762             show_bytes(debugFP, message, count);
1763             fprintf(debugFP, "\n");
1764         }
1765         return OutputToProcess(pr, message, count, outError);
1766     }
1767
1768     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1769     p = message;
1770     q = buf;
1771     left = count;
1772     newcount = 0;
1773     while (left) {
1774         if (q >= buflim) {
1775             if (appData.debugMode) {
1776                 fprintf(debugFP, ">ICS: ");
1777                 show_bytes(debugFP, buf, newcount);
1778                 fprintf(debugFP, "\n");
1779             }
1780             outcount = OutputToProcess(pr, buf, newcount, outError);
1781             if (outcount < newcount) return -1; /* to be sure */
1782             q = buf;
1783             newcount = 0;
1784         }
1785         if (*p == '\n') {
1786             *q++ = '\r';
1787             newcount++;
1788         } else if (((unsigned char) *p) == TN_IAC) {
1789             *q++ = (char) TN_IAC;
1790             newcount ++;
1791         }
1792         *q++ = *p++;
1793         newcount++;
1794         left--;
1795     }
1796     if (appData.debugMode) {
1797         fprintf(debugFP, ">ICS: ");
1798         show_bytes(debugFP, buf, newcount);
1799         fprintf(debugFP, "\n");
1800     }
1801     outcount = OutputToProcess(pr, buf, newcount, outError);
1802     if (outcount < newcount) return -1; /* to be sure */
1803     return count;
1804 }
1805
1806 void
1807 read_from_player(isr, closure, message, count, error)
1808      InputSourceRef isr;
1809      VOIDSTAR closure;
1810      char *message;
1811      int count;
1812      int error;
1813 {
1814     int outError, outCount;
1815     static int gotEof = 0;
1816
1817     /* Pass data read from player on to ICS */
1818     if (count > 0) {
1819         gotEof = 0;
1820         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1821         if (outCount < count) {
1822             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1823         }
1824     } else if (count < 0) {
1825         RemoveInputSource(isr);
1826         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1827     } else if (gotEof++ > 0) {
1828         RemoveInputSource(isr);
1829         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1830     }
1831 }
1832
1833 void
1834 KeepAlive()
1835 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1836     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1837     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1838     SendToICS("date\n");
1839     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1840 }
1841
1842 /* added routine for printf style output to ics */
1843 void ics_printf(char *format, ...)
1844 {
1845     char buffer[MSG_SIZ];
1846     va_list args;
1847
1848     va_start(args, format);
1849     vsnprintf(buffer, sizeof(buffer), format, args);
1850     buffer[sizeof(buffer)-1] = '\0';
1851     SendToICS(buffer);
1852     va_end(args);
1853 }
1854
1855 void
1856 SendToICS(s)
1857      char *s;
1858 {
1859     int count, outCount, outError;
1860
1861     if (icsPR == NoProc) return;
1862
1863     count = strlen(s);
1864     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1865     if (outCount < count) {
1866         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1867     }
1868 }
1869
1870 /* This is used for sending logon scripts to the ICS. Sending
1871    without a delay causes problems when using timestamp on ICC
1872    (at least on my machine). */
1873 void
1874 SendToICSDelayed(s,msdelay)
1875      char *s;
1876      long msdelay;
1877 {
1878     int count, outCount, outError;
1879
1880     if (icsPR == NoProc) return;
1881
1882     count = strlen(s);
1883     if (appData.debugMode) {
1884         fprintf(debugFP, ">ICS: ");
1885         show_bytes(debugFP, s, count);
1886         fprintf(debugFP, "\n");
1887     }
1888     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1889                                       msdelay);
1890     if (outCount < count) {
1891         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1892     }
1893 }
1894
1895
1896 /* Remove all highlighting escape sequences in s
1897    Also deletes any suffix starting with '('
1898    */
1899 char *
1900 StripHighlightAndTitle(s)
1901      char *s;
1902 {
1903     static char retbuf[MSG_SIZ];
1904     char *p = retbuf;
1905
1906     while (*s != NULLCHAR) {
1907         while (*s == '\033') {
1908             while (*s != NULLCHAR && !isalpha(*s)) s++;
1909             if (*s != NULLCHAR) s++;
1910         }
1911         while (*s != NULLCHAR && *s != '\033') {
1912             if (*s == '(' || *s == '[') {
1913                 *p = NULLCHAR;
1914                 return retbuf;
1915             }
1916             *p++ = *s++;
1917         }
1918     }
1919     *p = NULLCHAR;
1920     return retbuf;
1921 }
1922
1923 /* Remove all highlighting escape sequences in s */
1924 char *
1925 StripHighlight(s)
1926      char *s;
1927 {
1928     static char retbuf[MSG_SIZ];
1929     char *p = retbuf;
1930
1931     while (*s != NULLCHAR) {
1932         while (*s == '\033') {
1933             while (*s != NULLCHAR && !isalpha(*s)) s++;
1934             if (*s != NULLCHAR) s++;
1935         }
1936         while (*s != NULLCHAR && *s != '\033') {
1937             *p++ = *s++;
1938         }
1939     }
1940     *p = NULLCHAR;
1941     return retbuf;
1942 }
1943
1944 char *variantNames[] = VARIANT_NAMES;
1945 char *
1946 VariantName(v)
1947      VariantClass v;
1948 {
1949     return variantNames[v];
1950 }
1951
1952
1953 /* Identify a variant from the strings the chess servers use or the
1954    PGN Variant tag names we use. */
1955 VariantClass
1956 StringToVariant(e)
1957      char *e;
1958 {
1959     char *p;
1960     int wnum = -1;
1961     VariantClass v = VariantNormal;
1962     int i, found = FALSE;
1963     char buf[MSG_SIZ];
1964     int len;
1965
1966     if (!e) return v;
1967
1968     /* [HGM] skip over optional board-size prefixes */
1969     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1970         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1971         while( *e++ != '_');
1972     }
1973
1974     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1975         v = VariantNormal;
1976         found = TRUE;
1977     } else
1978     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1979       if (StrCaseStr(e, variantNames[i])) {
1980         v = (VariantClass) i;
1981         found = TRUE;
1982         break;
1983       }
1984     }
1985
1986     if (!found) {
1987       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1988           || StrCaseStr(e, "wild/fr")
1989           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1990         v = VariantFischeRandom;
1991       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1992                  (i = 1, p = StrCaseStr(e, "w"))) {
1993         p += i;
1994         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1995         if (isdigit(*p)) {
1996           wnum = atoi(p);
1997         } else {
1998           wnum = -1;
1999         }
2000         switch (wnum) {
2001         case 0: /* FICS only, actually */
2002         case 1:
2003           /* Castling legal even if K starts on d-file */
2004           v = VariantWildCastle;
2005           break;
2006         case 2:
2007         case 3:
2008         case 4:
2009           /* Castling illegal even if K & R happen to start in
2010              normal positions. */
2011           v = VariantNoCastle;
2012           break;
2013         case 5:
2014         case 7:
2015         case 8:
2016         case 10:
2017         case 11:
2018         case 12:
2019         case 13:
2020         case 14:
2021         case 15:
2022         case 18:
2023         case 19:
2024           /* Castling legal iff K & R start in normal positions */
2025           v = VariantNormal;
2026           break;
2027         case 6:
2028         case 20:
2029         case 21:
2030           /* Special wilds for position setup; unclear what to do here */
2031           v = VariantLoadable;
2032           break;
2033         case 9:
2034           /* Bizarre ICC game */
2035           v = VariantTwoKings;
2036           break;
2037         case 16:
2038           v = VariantKriegspiel;
2039           break;
2040         case 17:
2041           v = VariantLosers;
2042           break;
2043         case 22:
2044           v = VariantFischeRandom;
2045           break;
2046         case 23:
2047           v = VariantCrazyhouse;
2048           break;
2049         case 24:
2050           v = VariantBughouse;
2051           break;
2052         case 25:
2053           v = Variant3Check;
2054           break;
2055         case 26:
2056           /* Not quite the same as FICS suicide! */
2057           v = VariantGiveaway;
2058           break;
2059         case 27:
2060           v = VariantAtomic;
2061           break;
2062         case 28:
2063           v = VariantShatranj;
2064           break;
2065
2066         /* Temporary names for future ICC types.  The name *will* change in
2067            the next xboard/WinBoard release after ICC defines it. */
2068         case 29:
2069           v = Variant29;
2070           break;
2071         case 30:
2072           v = Variant30;
2073           break;
2074         case 31:
2075           v = Variant31;
2076           break;
2077         case 32:
2078           v = Variant32;
2079           break;
2080         case 33:
2081           v = Variant33;
2082           break;
2083         case 34:
2084           v = Variant34;
2085           break;
2086         case 35:
2087           v = Variant35;
2088           break;
2089         case 36:
2090           v = Variant36;
2091           break;
2092         case 37:
2093           v = VariantShogi;
2094           break;
2095         case 38:
2096           v = VariantXiangqi;
2097           break;
2098         case 39:
2099           v = VariantCourier;
2100           break;
2101         case 40:
2102           v = VariantGothic;
2103           break;
2104         case 41:
2105           v = VariantCapablanca;
2106           break;
2107         case 42:
2108           v = VariantKnightmate;
2109           break;
2110         case 43:
2111           v = VariantFairy;
2112           break;
2113         case 44:
2114           v = VariantCylinder;
2115           break;
2116         case 45:
2117           v = VariantFalcon;
2118           break;
2119         case 46:
2120           v = VariantCapaRandom;
2121           break;
2122         case 47:
2123           v = VariantBerolina;
2124           break;
2125         case 48:
2126           v = VariantJanus;
2127           break;
2128         case 49:
2129           v = VariantSuper;
2130           break;
2131         case 50:
2132           v = VariantGreat;
2133           break;
2134         case -1:
2135           /* Found "wild" or "w" in the string but no number;
2136              must assume it's normal chess. */
2137           v = VariantNormal;
2138           break;
2139         default:
2140           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2141           if( (len >= MSG_SIZ) && appData.debugMode )
2142             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2143
2144           DisplayError(buf, 0);
2145           v = VariantUnknown;
2146           break;
2147         }
2148       }
2149     }
2150     if (appData.debugMode) {
2151       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2152               e, wnum, VariantName(v));
2153     }
2154     return v;
2155 }
2156
2157 static int leftover_start = 0, leftover_len = 0;
2158 char star_match[STAR_MATCH_N][MSG_SIZ];
2159
2160 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2161    advance *index beyond it, and set leftover_start to the new value of
2162    *index; else return FALSE.  If pattern contains the character '*', it
2163    matches any sequence of characters not containing '\r', '\n', or the
2164    character following the '*' (if any), and the matched sequence(s) are
2165    copied into star_match.
2166    */
2167 int
2168 looking_at(buf, index, pattern)
2169      char *buf;
2170      int *index;
2171      char *pattern;
2172 {
2173     char *bufp = &buf[*index], *patternp = pattern;
2174     int star_count = 0;
2175     char *matchp = star_match[0];
2176
2177     for (;;) {
2178         if (*patternp == NULLCHAR) {
2179             *index = leftover_start = bufp - buf;
2180             *matchp = NULLCHAR;
2181             return TRUE;
2182         }
2183         if (*bufp == NULLCHAR) return FALSE;
2184         if (*patternp == '*') {
2185             if (*bufp == *(patternp + 1)) {
2186                 *matchp = NULLCHAR;
2187                 matchp = star_match[++star_count];
2188                 patternp += 2;
2189                 bufp++;
2190                 continue;
2191             } else if (*bufp == '\n' || *bufp == '\r') {
2192                 patternp++;
2193                 if (*patternp == NULLCHAR)
2194                   continue;
2195                 else
2196                   return FALSE;
2197             } else {
2198                 *matchp++ = *bufp++;
2199                 continue;
2200             }
2201         }
2202         if (*patternp != *bufp) return FALSE;
2203         patternp++;
2204         bufp++;
2205     }
2206 }
2207
2208 void
2209 SendToPlayer(data, length)
2210      char *data;
2211      int length;
2212 {
2213     int error, outCount;
2214     outCount = OutputToProcess(NoProc, data, length, &error);
2215     if (outCount < length) {
2216         DisplayFatalError(_("Error writing to display"), error, 1);
2217     }
2218 }
2219
2220 void
2221 PackHolding(packed, holding)
2222      char packed[];
2223      char *holding;
2224 {
2225     char *p = holding;
2226     char *q = packed;
2227     int runlength = 0;
2228     int curr = 9999;
2229     do {
2230         if (*p == curr) {
2231             runlength++;
2232         } else {
2233             switch (runlength) {
2234               case 0:
2235                 break;
2236               case 1:
2237                 *q++ = curr;
2238                 break;
2239               case 2:
2240                 *q++ = curr;
2241                 *q++ = curr;
2242                 break;
2243               default:
2244                 sprintf(q, "%d", runlength);
2245                 while (*q) q++;
2246                 *q++ = curr;
2247                 break;
2248             }
2249             runlength = 1;
2250             curr = *p;
2251         }
2252     } while (*p++);
2253     *q = NULLCHAR;
2254 }
2255
2256 /* Telnet protocol requests from the front end */
2257 void
2258 TelnetRequest(ddww, option)
2259      unsigned char ddww, option;
2260 {
2261     unsigned char msg[3];
2262     int outCount, outError;
2263
2264     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2265
2266     if (appData.debugMode) {
2267         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2268         switch (ddww) {
2269           case TN_DO:
2270             ddwwStr = "DO";
2271             break;
2272           case TN_DONT:
2273             ddwwStr = "DONT";
2274             break;
2275           case TN_WILL:
2276             ddwwStr = "WILL";
2277             break;
2278           case TN_WONT:
2279             ddwwStr = "WONT";
2280             break;
2281           default:
2282             ddwwStr = buf1;
2283             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2284             break;
2285         }
2286         switch (option) {
2287           case TN_ECHO:
2288             optionStr = "ECHO";
2289             break;
2290           default:
2291             optionStr = buf2;
2292             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2293             break;
2294         }
2295         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2296     }
2297     msg[0] = TN_IAC;
2298     msg[1] = ddww;
2299     msg[2] = option;
2300     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2301     if (outCount < 3) {
2302         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2303     }
2304 }
2305
2306 void
2307 DoEcho()
2308 {
2309     if (!appData.icsActive) return;
2310     TelnetRequest(TN_DO, TN_ECHO);
2311 }
2312
2313 void
2314 DontEcho()
2315 {
2316     if (!appData.icsActive) return;
2317     TelnetRequest(TN_DONT, TN_ECHO);
2318 }
2319
2320 void
2321 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2322 {
2323     /* put the holdings sent to us by the server on the board holdings area */
2324     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2325     char p;
2326     ChessSquare piece;
2327
2328     if(gameInfo.holdingsWidth < 2)  return;
2329     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2330         return; // prevent overwriting by pre-board holdings
2331
2332     if( (int)lowestPiece >= BlackPawn ) {
2333         holdingsColumn = 0;
2334         countsColumn = 1;
2335         holdingsStartRow = BOARD_HEIGHT-1;
2336         direction = -1;
2337     } else {
2338         holdingsColumn = BOARD_WIDTH-1;
2339         countsColumn = BOARD_WIDTH-2;
2340         holdingsStartRow = 0;
2341         direction = 1;
2342     }
2343
2344     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2345         board[i][holdingsColumn] = EmptySquare;
2346         board[i][countsColumn]   = (ChessSquare) 0;
2347     }
2348     while( (p=*holdings++) != NULLCHAR ) {
2349         piece = CharToPiece( ToUpper(p) );
2350         if(piece == EmptySquare) continue;
2351         /*j = (int) piece - (int) WhitePawn;*/
2352         j = PieceToNumber(piece);
2353         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2354         if(j < 0) continue;               /* should not happen */
2355         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2356         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2357         board[holdingsStartRow+j*direction][countsColumn]++;
2358     }
2359 }
2360
2361
2362 void
2363 VariantSwitch(Board board, VariantClass newVariant)
2364 {
2365    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2366    static Board oldBoard;
2367
2368    startedFromPositionFile = FALSE;
2369    if(gameInfo.variant == newVariant) return;
2370
2371    /* [HGM] This routine is called each time an assignment is made to
2372     * gameInfo.variant during a game, to make sure the board sizes
2373     * are set to match the new variant. If that means adding or deleting
2374     * holdings, we shift the playing board accordingly
2375     * This kludge is needed because in ICS observe mode, we get boards
2376     * of an ongoing game without knowing the variant, and learn about the
2377     * latter only later. This can be because of the move list we requested,
2378     * in which case the game history is refilled from the beginning anyway,
2379     * but also when receiving holdings of a crazyhouse game. In the latter
2380     * case we want to add those holdings to the already received position.
2381     */
2382
2383
2384    if (appData.debugMode) {
2385      fprintf(debugFP, "Switch board from %s to %s\n",
2386              VariantName(gameInfo.variant), VariantName(newVariant));
2387      setbuf(debugFP, NULL);
2388    }
2389    shuffleOpenings = 0;       /* [HGM] shuffle */
2390    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2391    switch(newVariant)
2392      {
2393      case VariantShogi:
2394        newWidth = 9;  newHeight = 9;
2395        gameInfo.holdingsSize = 7;
2396      case VariantBughouse:
2397      case VariantCrazyhouse:
2398        newHoldingsWidth = 2; break;
2399      case VariantGreat:
2400        newWidth = 10;
2401      case VariantSuper:
2402        newHoldingsWidth = 2;
2403        gameInfo.holdingsSize = 8;
2404        break;
2405      case VariantGothic:
2406      case VariantCapablanca:
2407      case VariantCapaRandom:
2408        newWidth = 10;
2409      default:
2410        newHoldingsWidth = gameInfo.holdingsSize = 0;
2411      };
2412
2413    if(newWidth  != gameInfo.boardWidth  ||
2414       newHeight != gameInfo.boardHeight ||
2415       newHoldingsWidth != gameInfo.holdingsWidth ) {
2416
2417      /* shift position to new playing area, if needed */
2418      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2419        for(i=0; i<BOARD_HEIGHT; i++)
2420          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2421            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2422              board[i][j];
2423        for(i=0; i<newHeight; i++) {
2424          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2425          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2426        }
2427      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2428        for(i=0; i<BOARD_HEIGHT; i++)
2429          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2430            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2431              board[i][j];
2432      }
2433      gameInfo.boardWidth  = newWidth;
2434      gameInfo.boardHeight = newHeight;
2435      gameInfo.holdingsWidth = newHoldingsWidth;
2436      gameInfo.variant = newVariant;
2437      InitDrawingSizes(-2, 0);
2438    } else gameInfo.variant = newVariant;
2439    CopyBoard(oldBoard, board);   // remember correctly formatted board
2440      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2441    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2442 }
2443
2444 static int loggedOn = FALSE;
2445
2446 /*-- Game start info cache: --*/
2447 int gs_gamenum;
2448 char gs_kind[MSG_SIZ];
2449 static char player1Name[128] = "";
2450 static char player2Name[128] = "";
2451 static char cont_seq[] = "\n\\   ";
2452 static int player1Rating = -1;
2453 static int player2Rating = -1;
2454 /*----------------------------*/
2455
2456 ColorClass curColor = ColorNormal;
2457 int suppressKibitz = 0;
2458
2459 // [HGM] seekgraph
2460 Boolean soughtPending = FALSE;
2461 Boolean seekGraphUp;
2462 #define MAX_SEEK_ADS 200
2463 #define SQUARE 0x80
2464 char *seekAdList[MAX_SEEK_ADS];
2465 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2466 float tcList[MAX_SEEK_ADS];
2467 char colorList[MAX_SEEK_ADS];
2468 int nrOfSeekAds = 0;
2469 int minRating = 1010, maxRating = 2800;
2470 int hMargin = 10, vMargin = 20, h, w;
2471 extern int squareSize, lineGap;
2472
2473 void
2474 PlotSeekAd(int i)
2475 {
2476         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2477         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2478         if(r < minRating+100 && r >=0 ) r = minRating+100;
2479         if(r > maxRating) r = maxRating;
2480         if(tc < 1.) tc = 1.;
2481         if(tc > 95.) tc = 95.;
2482         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2483         y = ((double)r - minRating)/(maxRating - minRating)
2484             * (h-vMargin-squareSize/8-1) + vMargin;
2485         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2486         if(strstr(seekAdList[i], " u ")) color = 1;
2487         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2488            !strstr(seekAdList[i], "bullet") &&
2489            !strstr(seekAdList[i], "blitz") &&
2490            !strstr(seekAdList[i], "standard") ) color = 2;
2491         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2492         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2493 }
2494
2495 void
2496 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2497 {
2498         char buf[MSG_SIZ], *ext = "";
2499         VariantClass v = StringToVariant(type);
2500         if(strstr(type, "wild")) {
2501             ext = type + 4; // append wild number
2502             if(v == VariantFischeRandom) type = "chess960"; else
2503             if(v == VariantLoadable) type = "setup"; else
2504             type = VariantName(v);
2505         }
2506         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2507         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2508             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2509             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2510             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2511             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2512             seekNrList[nrOfSeekAds] = nr;
2513             zList[nrOfSeekAds] = 0;
2514             seekAdList[nrOfSeekAds++] = StrSave(buf);
2515             if(plot) PlotSeekAd(nrOfSeekAds-1);
2516         }
2517 }
2518
2519 void
2520 EraseSeekDot(int i)
2521 {
2522     int x = xList[i], y = yList[i], d=squareSize/4, k;
2523     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2524     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2525     // now replot every dot that overlapped
2526     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2527         int xx = xList[k], yy = yList[k];
2528         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2529             DrawSeekDot(xx, yy, colorList[k]);
2530     }
2531 }
2532
2533 void
2534 RemoveSeekAd(int nr)
2535 {
2536         int i;
2537         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2538             EraseSeekDot(i);
2539             if(seekAdList[i]) free(seekAdList[i]);
2540             seekAdList[i] = seekAdList[--nrOfSeekAds];
2541             seekNrList[i] = seekNrList[nrOfSeekAds];
2542             ratingList[i] = ratingList[nrOfSeekAds];
2543             colorList[i]  = colorList[nrOfSeekAds];
2544             tcList[i] = tcList[nrOfSeekAds];
2545             xList[i]  = xList[nrOfSeekAds];
2546             yList[i]  = yList[nrOfSeekAds];
2547             zList[i]  = zList[nrOfSeekAds];
2548             seekAdList[nrOfSeekAds] = NULL;
2549             break;
2550         }
2551 }
2552
2553 Boolean
2554 MatchSoughtLine(char *line)
2555 {
2556     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2557     int nr, base, inc, u=0; char dummy;
2558
2559     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2560        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2561        (u=1) &&
2562        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2563         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2564         // match: compact and save the line
2565         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2566         return TRUE;
2567     }
2568     return FALSE;
2569 }
2570
2571 int
2572 DrawSeekGraph()
2573 {
2574     int i;
2575     if(!seekGraphUp) return FALSE;
2576     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2577     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2578
2579     DrawSeekBackground(0, 0, w, h);
2580     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2581     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2582     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2583         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2584         yy = h-1-yy;
2585         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2586         if(i%500 == 0) {
2587             char buf[MSG_SIZ];
2588             snprintf(buf, MSG_SIZ, "%d", i);
2589             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2590         }
2591     }
2592     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2593     for(i=1; i<100; i+=(i<10?1:5)) {
2594         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2595         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2596         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2597             char buf[MSG_SIZ];
2598             snprintf(buf, MSG_SIZ, "%d", i);
2599             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2600         }
2601     }
2602     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2603     return TRUE;
2604 }
2605
2606 int SeekGraphClick(ClickType click, int x, int y, int moving)
2607 {
2608     static int lastDown = 0, displayed = 0, lastSecond;
2609     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2610         if(click == Release || moving) return FALSE;
2611         nrOfSeekAds = 0;
2612         soughtPending = TRUE;
2613         SendToICS(ics_prefix);
2614         SendToICS("sought\n"); // should this be "sought all"?
2615     } else { // issue challenge based on clicked ad
2616         int dist = 10000; int i, closest = 0, second = 0;
2617         for(i=0; i<nrOfSeekAds; i++) {
2618             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2619             if(d < dist) { dist = d; closest = i; }
2620             second += (d - zList[i] < 120); // count in-range ads
2621             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2622         }
2623         if(dist < 120) {
2624             char buf[MSG_SIZ];
2625             second = (second > 1);
2626             if(displayed != closest || second != lastSecond) {
2627                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2628                 lastSecond = second; displayed = closest;
2629             }
2630             if(click == Press) {
2631                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2632                 lastDown = closest;
2633                 return TRUE;
2634             } // on press 'hit', only show info
2635             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2636             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2637             SendToICS(ics_prefix);
2638             SendToICS(buf);
2639             return TRUE; // let incoming board of started game pop down the graph
2640         } else if(click == Release) { // release 'miss' is ignored
2641             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2642             if(moving == 2) { // right up-click
2643                 nrOfSeekAds = 0; // refresh graph
2644                 soughtPending = TRUE;
2645                 SendToICS(ics_prefix);
2646                 SendToICS("sought\n"); // should this be "sought all"?
2647             }
2648             return TRUE;
2649         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2650         // press miss or release hit 'pop down' seek graph
2651         seekGraphUp = FALSE;
2652         DrawPosition(TRUE, NULL);
2653     }
2654     return TRUE;
2655 }
2656
2657 void
2658 read_from_ics(isr, closure, data, count, error)
2659      InputSourceRef isr;
2660      VOIDSTAR closure;
2661      char *data;
2662      int count;
2663      int error;
2664 {
2665 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2666 #define STARTED_NONE 0
2667 #define STARTED_MOVES 1
2668 #define STARTED_BOARD 2
2669 #define STARTED_OBSERVE 3
2670 #define STARTED_HOLDINGS 4
2671 #define STARTED_CHATTER 5
2672 #define STARTED_COMMENT 6
2673 #define STARTED_MOVES_NOHIDE 7
2674
2675     static int started = STARTED_NONE;
2676     static char parse[20000];
2677     static int parse_pos = 0;
2678     static char buf[BUF_SIZE + 1];
2679     static int firstTime = TRUE, intfSet = FALSE;
2680     static ColorClass prevColor = ColorNormal;
2681     static int savingComment = FALSE;
2682     static int cmatch = 0; // continuation sequence match
2683     char *bp;
2684     char str[MSG_SIZ];
2685     int i, oldi;
2686     int buf_len;
2687     int next_out;
2688     int tkind;
2689     int backup;    /* [DM] For zippy color lines */
2690     char *p;
2691     char talker[MSG_SIZ]; // [HGM] chat
2692     int channel;
2693
2694     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2695
2696     if (appData.debugMode) {
2697       if (!error) {
2698         fprintf(debugFP, "<ICS: ");
2699         show_bytes(debugFP, data, count);
2700         fprintf(debugFP, "\n");
2701       }
2702     }
2703
2704     if (appData.debugMode) { int f = forwardMostMove;
2705         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2706                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2707                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2708     }
2709     if (count > 0) {
2710         /* If last read ended with a partial line that we couldn't parse,
2711            prepend it to the new read and try again. */
2712         if (leftover_len > 0) {
2713             for (i=0; i<leftover_len; i++)
2714               buf[i] = buf[leftover_start + i];
2715         }
2716
2717     /* copy new characters into the buffer */
2718     bp = buf + leftover_len;
2719     buf_len=leftover_len;
2720     for (i=0; i<count; i++)
2721     {
2722         // ignore these
2723         if (data[i] == '\r')
2724             continue;
2725
2726         // join lines split by ICS?
2727         if (!appData.noJoin)
2728         {
2729             /*
2730                 Joining just consists of finding matches against the
2731                 continuation sequence, and discarding that sequence
2732                 if found instead of copying it.  So, until a match
2733                 fails, there's nothing to do since it might be the
2734                 complete sequence, and thus, something we don't want
2735                 copied.
2736             */
2737             if (data[i] == cont_seq[cmatch])
2738             {
2739                 cmatch++;
2740                 if (cmatch == strlen(cont_seq))
2741                 {
2742                     cmatch = 0; // complete match.  just reset the counter
2743
2744                     /*
2745                         it's possible for the ICS to not include the space
2746                         at the end of the last word, making our [correct]
2747                         join operation fuse two separate words.  the server
2748                         does this when the space occurs at the width setting.
2749                     */
2750                     if (!buf_len || buf[buf_len-1] != ' ')
2751                     {
2752                         *bp++ = ' ';
2753                         buf_len++;
2754                     }
2755                 }
2756                 continue;
2757             }
2758             else if (cmatch)
2759             {
2760                 /*
2761                     match failed, so we have to copy what matched before
2762                     falling through and copying this character.  In reality,
2763                     this will only ever be just the newline character, but
2764                     it doesn't hurt to be precise.
2765                 */
2766                 strncpy(bp, cont_seq, cmatch);
2767                 bp += cmatch;
2768                 buf_len += cmatch;
2769                 cmatch = 0;
2770             }
2771         }
2772
2773         // copy this char
2774         *bp++ = data[i];
2775         buf_len++;
2776     }
2777
2778         buf[buf_len] = NULLCHAR;
2779 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2780         next_out = 0;
2781         leftover_start = 0;
2782
2783         i = 0;
2784         while (i < buf_len) {
2785             /* Deal with part of the TELNET option negotiation
2786                protocol.  We refuse to do anything beyond the
2787                defaults, except that we allow the WILL ECHO option,
2788                which ICS uses to turn off password echoing when we are
2789                directly connected to it.  We reject this option
2790                if localLineEditing mode is on (always on in xboard)
2791                and we are talking to port 23, which might be a real
2792                telnet server that will try to keep WILL ECHO on permanently.
2793              */
2794             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2795                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2796                 unsigned char option;
2797                 oldi = i;
2798                 switch ((unsigned char) buf[++i]) {
2799                   case TN_WILL:
2800                     if (appData.debugMode)
2801                       fprintf(debugFP, "\n<WILL ");
2802                     switch (option = (unsigned char) buf[++i]) {
2803                       case TN_ECHO:
2804                         if (appData.debugMode)
2805                           fprintf(debugFP, "ECHO ");
2806                         /* Reply only if this is a change, according
2807                            to the protocol rules. */
2808                         if (remoteEchoOption) break;
2809                         if (appData.localLineEditing &&
2810                             atoi(appData.icsPort) == TN_PORT) {
2811                             TelnetRequest(TN_DONT, TN_ECHO);
2812                         } else {
2813                             EchoOff();
2814                             TelnetRequest(TN_DO, TN_ECHO);
2815                             remoteEchoOption = TRUE;
2816                         }
2817                         break;
2818                       default:
2819                         if (appData.debugMode)
2820                           fprintf(debugFP, "%d ", option);
2821                         /* Whatever this is, we don't want it. */
2822                         TelnetRequest(TN_DONT, option);
2823                         break;
2824                     }
2825                     break;
2826                   case TN_WONT:
2827                     if (appData.debugMode)
2828                       fprintf(debugFP, "\n<WONT ");
2829                     switch (option = (unsigned char) buf[++i]) {
2830                       case TN_ECHO:
2831                         if (appData.debugMode)
2832                           fprintf(debugFP, "ECHO ");
2833                         /* Reply only if this is a change, according
2834                            to the protocol rules. */
2835                         if (!remoteEchoOption) break;
2836                         EchoOn();
2837                         TelnetRequest(TN_DONT, TN_ECHO);
2838                         remoteEchoOption = FALSE;
2839                         break;
2840                       default:
2841                         if (appData.debugMode)
2842                           fprintf(debugFP, "%d ", (unsigned char) option);
2843                         /* Whatever this is, it must already be turned
2844                            off, because we never agree to turn on
2845                            anything non-default, so according to the
2846                            protocol rules, we don't reply. */
2847                         break;
2848                     }
2849                     break;
2850                   case TN_DO:
2851                     if (appData.debugMode)
2852                       fprintf(debugFP, "\n<DO ");
2853                     switch (option = (unsigned char) buf[++i]) {
2854                       default:
2855                         /* Whatever this is, we refuse to do it. */
2856                         if (appData.debugMode)
2857                           fprintf(debugFP, "%d ", option);
2858                         TelnetRequest(TN_WONT, option);
2859                         break;
2860                     }
2861                     break;
2862                   case TN_DONT:
2863                     if (appData.debugMode)
2864                       fprintf(debugFP, "\n<DONT ");
2865                     switch (option = (unsigned char) buf[++i]) {
2866                       default:
2867                         if (appData.debugMode)
2868                           fprintf(debugFP, "%d ", option);
2869                         /* Whatever this is, we are already not doing
2870                            it, because we never agree to do anything
2871                            non-default, so according to the protocol
2872                            rules, we don't reply. */
2873                         break;
2874                     }
2875                     break;
2876                   case TN_IAC:
2877                     if (appData.debugMode)
2878                       fprintf(debugFP, "\n<IAC ");
2879                     /* Doubled IAC; pass it through */
2880                     i--;
2881                     break;
2882                   default:
2883                     if (appData.debugMode)
2884                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2885                     /* Drop all other telnet commands on the floor */
2886                     break;
2887                 }
2888                 if (oldi > next_out)
2889                   SendToPlayer(&buf[next_out], oldi - next_out);
2890                 if (++i > next_out)
2891                   next_out = i;
2892                 continue;
2893             }
2894
2895             /* OK, this at least will *usually* work */
2896             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2897                 loggedOn = TRUE;
2898             }
2899
2900             if (loggedOn && !intfSet) {
2901                 if (ics_type == ICS_ICC) {
2902                   snprintf(str, MSG_SIZ,
2903                           "/set-quietly interface %s\n/set-quietly style 12\n",
2904                           programVersion);
2905                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2906                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2907                 } else if (ics_type == ICS_CHESSNET) {
2908                   snprintf(str, MSG_SIZ, "/style 12\n");
2909                 } else {
2910                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2911                   strcat(str, programVersion);
2912                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2913                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2914                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2915 #ifdef WIN32
2916                   strcat(str, "$iset nohighlight 1\n");
2917 #endif
2918                   strcat(str, "$iset lock 1\n$style 12\n");
2919                 }
2920                 SendToICS(str);
2921                 NotifyFrontendLogin();
2922                 intfSet = TRUE;
2923             }
2924
2925             if (started == STARTED_COMMENT) {
2926                 /* Accumulate characters in comment */
2927                 parse[parse_pos++] = buf[i];
2928                 if (buf[i] == '\n') {
2929                     parse[parse_pos] = NULLCHAR;
2930                     if(chattingPartner>=0) {
2931                         char mess[MSG_SIZ];
2932                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2933                         OutputChatMessage(chattingPartner, mess);
2934                         chattingPartner = -1;
2935                         next_out = i+1; // [HGM] suppress printing in ICS window
2936                     } else
2937                     if(!suppressKibitz) // [HGM] kibitz
2938                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2939                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2940                         int nrDigit = 0, nrAlph = 0, j;
2941                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2942                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2943                         parse[parse_pos] = NULLCHAR;
2944                         // try to be smart: if it does not look like search info, it should go to
2945                         // ICS interaction window after all, not to engine-output window.
2946                         for(j=0; j<parse_pos; j++) { // count letters and digits
2947                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2948                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2949                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2950                         }
2951                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2952                             int depth=0; float score;
2953                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2954                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2955                                 pvInfoList[forwardMostMove-1].depth = depth;
2956                                 pvInfoList[forwardMostMove-1].score = 100*score;
2957                             }
2958                             OutputKibitz(suppressKibitz, parse);
2959                         } else {
2960                             char tmp[MSG_SIZ];
2961                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2962                             SendToPlayer(tmp, strlen(tmp));
2963                         }
2964                         next_out = i+1; // [HGM] suppress printing in ICS window
2965                     }
2966                     started = STARTED_NONE;
2967                 } else {
2968                     /* Don't match patterns against characters in comment */
2969                     i++;
2970                     continue;
2971                 }
2972             }
2973             if (started == STARTED_CHATTER) {
2974                 if (buf[i] != '\n') {
2975                     /* Don't match patterns against characters in chatter */
2976                     i++;
2977                     continue;
2978                 }
2979                 started = STARTED_NONE;
2980                 if(suppressKibitz) next_out = i+1;
2981             }
2982
2983             /* Kludge to deal with rcmd protocol */
2984             if (firstTime && looking_at(buf, &i, "\001*")) {
2985                 DisplayFatalError(&buf[1], 0, 1);
2986                 continue;
2987             } else {
2988                 firstTime = FALSE;
2989             }
2990
2991             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2992                 ics_type = ICS_ICC;
2993                 ics_prefix = "/";
2994                 if (appData.debugMode)
2995                   fprintf(debugFP, "ics_type %d\n", ics_type);
2996                 continue;
2997             }
2998             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2999                 ics_type = ICS_FICS;
3000                 ics_prefix = "$";
3001                 if (appData.debugMode)
3002                   fprintf(debugFP, "ics_type %d\n", ics_type);
3003                 continue;
3004             }
3005             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3006                 ics_type = ICS_CHESSNET;
3007                 ics_prefix = "/";
3008                 if (appData.debugMode)
3009                   fprintf(debugFP, "ics_type %d\n", ics_type);
3010                 continue;
3011             }
3012
3013             if (!loggedOn &&
3014                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3015                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3016                  looking_at(buf, &i, "will be \"*\""))) {
3017               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3018               continue;
3019             }
3020
3021             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3022               char buf[MSG_SIZ];
3023               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3024               DisplayIcsInteractionTitle(buf);
3025               have_set_title = TRUE;
3026             }
3027
3028             /* skip finger notes */
3029             if (started == STARTED_NONE &&
3030                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3031                  (buf[i] == '1' && buf[i+1] == '0')) &&
3032                 buf[i+2] == ':' && buf[i+3] == ' ') {
3033               started = STARTED_CHATTER;
3034               i += 3;
3035               continue;
3036             }
3037
3038             oldi = i;
3039             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3040             if(appData.seekGraph) {
3041                 if(soughtPending && MatchSoughtLine(buf+i)) {
3042                     i = strstr(buf+i, "rated") - buf;
3043                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3044                     next_out = leftover_start = i;
3045                     started = STARTED_CHATTER;
3046                     suppressKibitz = TRUE;
3047                     continue;
3048                 }
3049                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3050                         && looking_at(buf, &i, "* ads displayed")) {
3051                     soughtPending = FALSE;
3052                     seekGraphUp = TRUE;
3053                     DrawSeekGraph();
3054                     continue;
3055                 }
3056                 if(appData.autoRefresh) {
3057                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3058                         int s = (ics_type == ICS_ICC); // ICC format differs
3059                         if(seekGraphUp)
3060                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3061                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3062                         looking_at(buf, &i, "*% "); // eat prompt
3063                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3064                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3065                         next_out = i; // suppress
3066                         continue;
3067                     }
3068                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3069                         char *p = star_match[0];
3070                         while(*p) {
3071                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3072                             while(*p && *p++ != ' '); // next
3073                         }
3074                         looking_at(buf, &i, "*% "); // eat prompt
3075                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3076                         next_out = i;
3077                         continue;
3078                     }
3079                 }
3080             }
3081
3082             /* skip formula vars */
3083             if (started == STARTED_NONE &&
3084                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3085               started = STARTED_CHATTER;
3086               i += 3;
3087               continue;
3088             }
3089
3090             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3091             if (appData.autoKibitz && started == STARTED_NONE &&
3092                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3093                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3094                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3095                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3096                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3097                         suppressKibitz = TRUE;
3098                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3099                         next_out = i;
3100                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3101                                 && (gameMode == IcsPlayingWhite)) ||
3102                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3103                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3104                             started = STARTED_CHATTER; // own kibitz we simply discard
3105                         else {
3106                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3107                             parse_pos = 0; parse[0] = NULLCHAR;
3108                             savingComment = TRUE;
3109                             suppressKibitz = gameMode != IcsObserving ? 2 :
3110                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3111                         }
3112                         continue;
3113                 } else
3114                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3115                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3116                          && atoi(star_match[0])) {
3117                     // suppress the acknowledgements of our own autoKibitz
3118                     char *p;
3119                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3120                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3121                     SendToPlayer(star_match[0], strlen(star_match[0]));
3122                     if(looking_at(buf, &i, "*% ")) // eat prompt
3123                         suppressKibitz = FALSE;
3124                     next_out = i;
3125                     continue;
3126                 }
3127             } // [HGM] kibitz: end of patch
3128
3129             // [HGM] chat: intercept tells by users for which we have an open chat window
3130             channel = -1;
3131             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3132                                            looking_at(buf, &i, "* whispers:") ||
3133                                            looking_at(buf, &i, "* kibitzes:") ||
3134                                            looking_at(buf, &i, "* shouts:") ||
3135                                            looking_at(buf, &i, "* c-shouts:") ||
3136                                            looking_at(buf, &i, "--> * ") ||
3137                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3138                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3139                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3140                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3141                 int p;
3142                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3143                 chattingPartner = -1;
3144
3145                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3146                 for(p=0; p<MAX_CHAT; p++) {
3147                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3148                     talker[0] = '['; strcat(talker, "] ");
3149                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3150                     chattingPartner = p; break;
3151                     }
3152                 } else
3153                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3154                 for(p=0; p<MAX_CHAT; p++) {
3155                     if(!strcmp("kibitzes", chatPartner[p])) {
3156                         talker[0] = '['; strcat(talker, "] ");
3157                         chattingPartner = p; break;
3158                     }
3159                 } else
3160                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3161                 for(p=0; p<MAX_CHAT; p++) {
3162                     if(!strcmp("whispers", chatPartner[p])) {
3163                         talker[0] = '['; strcat(talker, "] ");
3164                         chattingPartner = p; break;
3165                     }
3166                 } else
3167                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3168                   if(buf[i-8] == '-' && buf[i-3] == 't')
3169                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3170                     if(!strcmp("c-shouts", chatPartner[p])) {
3171                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3172                         chattingPartner = p; break;
3173                     }
3174                   }
3175                   if(chattingPartner < 0)
3176                   for(p=0; p<MAX_CHAT; p++) {
3177                     if(!strcmp("shouts", chatPartner[p])) {
3178                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3179                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3180                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3181                         chattingPartner = p; break;
3182                     }
3183                   }
3184                 }
3185                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3186                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3187                     talker[0] = 0; Colorize(ColorTell, FALSE);
3188                     chattingPartner = p; break;
3189                 }
3190                 if(chattingPartner<0) i = oldi; else {
3191                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3192                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3193                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3194                     started = STARTED_COMMENT;
3195                     parse_pos = 0; parse[0] = NULLCHAR;
3196                     savingComment = 3 + chattingPartner; // counts as TRUE
3197                     suppressKibitz = TRUE;
3198                     continue;
3199                 }
3200             } // [HGM] chat: end of patch
3201
3202           backup = i;
3203             if (appData.zippyTalk || appData.zippyPlay) {
3204                 /* [DM] Backup address for color zippy lines */
3205 #if ZIPPY
3206                if (loggedOn == TRUE)
3207                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3208                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3209 #endif
3210             } // [DM] 'else { ' deleted
3211                 if (
3212                     /* Regular tells and says */
3213                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3214                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3215                     looking_at(buf, &i, "* says: ") ||
3216                     /* Don't color "message" or "messages" output */
3217                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3218                     looking_at(buf, &i, "*. * at *:*: ") ||
3219                     looking_at(buf, &i, "--* (*:*): ") ||
3220                     /* Message notifications (same color as tells) */
3221                     looking_at(buf, &i, "* has left a message ") ||
3222                     looking_at(buf, &i, "* just sent you a message:\n") ||
3223                     /* Whispers and kibitzes */
3224                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3225                     looking_at(buf, &i, "* kibitzes: ") ||
3226                     /* Channel tells */
3227                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3228
3229                   if (tkind == 1 && strchr(star_match[0], ':')) {
3230                       /* Avoid "tells you:" spoofs in channels */
3231                      tkind = 3;
3232                   }
3233                   if (star_match[0][0] == NULLCHAR ||
3234                       strchr(star_match[0], ' ') ||
3235                       (tkind == 3 && strchr(star_match[1], ' '))) {
3236                     /* Reject bogus matches */
3237                     i = oldi;
3238                   } else {
3239                     if (appData.colorize) {
3240                       if (oldi > next_out) {
3241                         SendToPlayer(&buf[next_out], oldi - next_out);
3242                         next_out = oldi;
3243                       }
3244                       switch (tkind) {
3245                       case 1:
3246                         Colorize(ColorTell, FALSE);
3247                         curColor = ColorTell;
3248                         break;
3249                       case 2:
3250                         Colorize(ColorKibitz, FALSE);
3251                         curColor = ColorKibitz;
3252                         break;
3253                       case 3:
3254                         p = strrchr(star_match[1], '(');
3255                         if (p == NULL) {
3256                           p = star_match[1];
3257                         } else {
3258                           p++;
3259                         }
3260                         if (atoi(p) == 1) {
3261                           Colorize(ColorChannel1, FALSE);
3262                           curColor = ColorChannel1;
3263                         } else {
3264                           Colorize(ColorChannel, FALSE);
3265                           curColor = ColorChannel;
3266                         }
3267                         break;
3268                       case 5:
3269                         curColor = ColorNormal;
3270                         break;
3271                       }
3272                     }
3273                     if (started == STARTED_NONE && appData.autoComment &&
3274                         (gameMode == IcsObserving ||
3275                          gameMode == IcsPlayingWhite ||
3276                          gameMode == IcsPlayingBlack)) {
3277                       parse_pos = i - oldi;
3278                       memcpy(parse, &buf[oldi], parse_pos);
3279                       parse[parse_pos] = NULLCHAR;
3280                       started = STARTED_COMMENT;
3281                       savingComment = TRUE;
3282                     } else {
3283                       started = STARTED_CHATTER;
3284                       savingComment = FALSE;
3285                     }
3286                     loggedOn = TRUE;
3287                     continue;
3288                   }
3289                 }
3290
3291                 if (looking_at(buf, &i, "* s-shouts: ") ||
3292                     looking_at(buf, &i, "* c-shouts: ")) {
3293                     if (appData.colorize) {
3294                         if (oldi > next_out) {
3295                             SendToPlayer(&buf[next_out], oldi - next_out);
3296                             next_out = oldi;
3297                         }
3298                         Colorize(ColorSShout, FALSE);
3299                         curColor = ColorSShout;
3300                     }
3301                     loggedOn = TRUE;
3302                     started = STARTED_CHATTER;
3303                     continue;
3304                 }
3305
3306                 if (looking_at(buf, &i, "--->")) {
3307                     loggedOn = TRUE;
3308                     continue;
3309                 }
3310
3311                 if (looking_at(buf, &i, "* shouts: ") ||
3312                     looking_at(buf, &i, "--> ")) {
3313                     if (appData.colorize) {
3314                         if (oldi > next_out) {
3315                             SendToPlayer(&buf[next_out], oldi - next_out);
3316                             next_out = oldi;
3317                         }
3318                         Colorize(ColorShout, FALSE);
3319                         curColor = ColorShout;
3320                     }
3321                     loggedOn = TRUE;
3322                     started = STARTED_CHATTER;
3323                     continue;
3324                 }
3325
3326                 if (looking_at( buf, &i, "Challenge:")) {
3327                     if (appData.colorize) {
3328                         if (oldi > next_out) {
3329                             SendToPlayer(&buf[next_out], oldi - next_out);
3330                             next_out = oldi;
3331                         }
3332                         Colorize(ColorChallenge, FALSE);
3333                         curColor = ColorChallenge;
3334                     }
3335                     loggedOn = TRUE;
3336                     continue;
3337                 }
3338
3339                 if (looking_at(buf, &i, "* offers you") ||
3340                     looking_at(buf, &i, "* offers to be") ||
3341                     looking_at(buf, &i, "* would like to") ||
3342                     looking_at(buf, &i, "* requests to") ||
3343                     looking_at(buf, &i, "Your opponent offers") ||
3344                     looking_at(buf, &i, "Your opponent requests")) {
3345
3346                     if (appData.colorize) {
3347                         if (oldi > next_out) {
3348                             SendToPlayer(&buf[next_out], oldi - next_out);
3349                             next_out = oldi;
3350                         }
3351                         Colorize(ColorRequest, FALSE);
3352                         curColor = ColorRequest;
3353                     }
3354                     continue;
3355                 }
3356
3357                 if (looking_at(buf, &i, "* (*) seeking")) {
3358                     if (appData.colorize) {
3359                         if (oldi > next_out) {
3360                             SendToPlayer(&buf[next_out], oldi - next_out);
3361                             next_out = oldi;
3362                         }
3363                         Colorize(ColorSeek, FALSE);
3364                         curColor = ColorSeek;
3365                     }
3366                     continue;
3367             }
3368
3369           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3370
3371             if (looking_at(buf, &i, "\\   ")) {
3372                 if (prevColor != ColorNormal) {
3373                     if (oldi > next_out) {
3374                         SendToPlayer(&buf[next_out], oldi - next_out);
3375                         next_out = oldi;
3376                     }
3377                     Colorize(prevColor, TRUE);
3378                     curColor = prevColor;
3379                 }
3380                 if (savingComment) {
3381                     parse_pos = i - oldi;
3382                     memcpy(parse, &buf[oldi], parse_pos);
3383                     parse[parse_pos] = NULLCHAR;
3384                     started = STARTED_COMMENT;
3385                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3386                         chattingPartner = savingComment - 3; // kludge to remember the box
3387                 } else {
3388                     started = STARTED_CHATTER;
3389                 }
3390                 continue;
3391             }
3392
3393             if (looking_at(buf, &i, "Black Strength :") ||
3394                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3395                 looking_at(buf, &i, "<10>") ||
3396                 looking_at(buf, &i, "#@#")) {
3397                 /* Wrong board style */
3398                 loggedOn = TRUE;
3399                 SendToICS(ics_prefix);
3400                 SendToICS("set style 12\n");
3401                 SendToICS(ics_prefix);
3402                 SendToICS("refresh\n");
3403                 continue;
3404             }
3405
3406             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3407                 ICSInitScript();
3408                 have_sent_ICS_logon = 1;
3409                 continue;
3410             }
3411
3412             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3413                 (looking_at(buf, &i, "\n<12> ") ||
3414                  looking_at(buf, &i, "<12> "))) {
3415                 loggedOn = TRUE;
3416                 if (oldi > next_out) {
3417                     SendToPlayer(&buf[next_out], oldi - next_out);
3418                 }
3419                 next_out = i;
3420                 started = STARTED_BOARD;
3421                 parse_pos = 0;
3422                 continue;
3423             }
3424
3425             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3426                 looking_at(buf, &i, "<b1> ")) {
3427                 if (oldi > next_out) {
3428                     SendToPlayer(&buf[next_out], oldi - next_out);
3429                 }
3430                 next_out = i;
3431                 started = STARTED_HOLDINGS;
3432                 parse_pos = 0;
3433                 continue;
3434             }
3435
3436             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3437                 loggedOn = TRUE;
3438                 /* Header for a move list -- first line */
3439
3440                 switch (ics_getting_history) {
3441                   case H_FALSE:
3442                     switch (gameMode) {
3443                       case IcsIdle:
3444                       case BeginningOfGame:
3445                         /* User typed "moves" or "oldmoves" while we
3446                            were idle.  Pretend we asked for these
3447                            moves and soak them up so user can step
3448                            through them and/or save them.
3449                            */
3450                         Reset(FALSE, TRUE);
3451                         gameMode = IcsObserving;
3452                         ModeHighlight();
3453                         ics_gamenum = -1;
3454                         ics_getting_history = H_GOT_UNREQ_HEADER;
3455                         break;
3456                       case EditGame: /*?*/
3457                       case EditPosition: /*?*/
3458                         /* Should above feature work in these modes too? */
3459                         /* For now it doesn't */
3460                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3461                         break;
3462                       default:
3463                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3464                         break;
3465                     }
3466                     break;
3467                   case H_REQUESTED:
3468                     /* Is this the right one? */
3469                     if (gameInfo.white && gameInfo.black &&
3470                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3471                         strcmp(gameInfo.black, star_match[2]) == 0) {
3472                         /* All is well */
3473                         ics_getting_history = H_GOT_REQ_HEADER;
3474                     }
3475                     break;
3476                   case H_GOT_REQ_HEADER:
3477                   case H_GOT_UNREQ_HEADER:
3478                   case H_GOT_UNWANTED_HEADER:
3479                   case H_GETTING_MOVES:
3480                     /* Should not happen */
3481                     DisplayError(_("Error gathering move list: two headers"), 0);
3482                     ics_getting_history = H_FALSE;
3483                     break;
3484                 }
3485
3486                 /* Save player ratings into gameInfo if needed */
3487                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3488                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3489                     (gameInfo.whiteRating == -1 ||
3490                      gameInfo.blackRating == -1)) {
3491
3492                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3493                     gameInfo.blackRating = string_to_rating(star_match[3]);
3494                     if (appData.debugMode)
3495                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3496                               gameInfo.whiteRating, gameInfo.blackRating);
3497                 }
3498                 continue;
3499             }
3500
3501             if (looking_at(buf, &i,
3502               "* * match, initial time: * minute*, increment: * second")) {
3503                 /* Header for a move list -- second line */
3504                 /* Initial board will follow if this is a wild game */
3505                 if (gameInfo.event != NULL) free(gameInfo.event);
3506                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3507                 gameInfo.event = StrSave(str);
3508                 /* [HGM] we switched variant. Translate boards if needed. */
3509                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3510                 continue;
3511             }
3512
3513             if (looking_at(buf, &i, "Move  ")) {
3514                 /* Beginning of a move list */
3515                 switch (ics_getting_history) {
3516                   case H_FALSE:
3517                     /* Normally should not happen */
3518                     /* Maybe user hit reset while we were parsing */
3519                     break;
3520                   case H_REQUESTED:
3521                     /* Happens if we are ignoring a move list that is not
3522                      * the one we just requested.  Common if the user
3523                      * tries to observe two games without turning off
3524                      * getMoveList */
3525                     break;
3526                   case H_GETTING_MOVES:
3527                     /* Should not happen */
3528                     DisplayError(_("Error gathering move list: nested"), 0);
3529                     ics_getting_history = H_FALSE;
3530                     break;
3531                   case H_GOT_REQ_HEADER:
3532                     ics_getting_history = H_GETTING_MOVES;
3533                     started = STARTED_MOVES;
3534                     parse_pos = 0;
3535                     if (oldi > next_out) {
3536                         SendToPlayer(&buf[next_out], oldi - next_out);
3537                     }
3538                     break;
3539                   case H_GOT_UNREQ_HEADER:
3540                     ics_getting_history = H_GETTING_MOVES;
3541                     started = STARTED_MOVES_NOHIDE;
3542                     parse_pos = 0;
3543                     break;
3544                   case H_GOT_UNWANTED_HEADER:
3545                     ics_getting_history = H_FALSE;
3546                     break;
3547                 }
3548                 continue;
3549             }
3550
3551             if (looking_at(buf, &i, "% ") ||
3552                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3553                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3554                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3555                     soughtPending = FALSE;
3556                     seekGraphUp = TRUE;
3557                     DrawSeekGraph();
3558                 }
3559                 if(suppressKibitz) next_out = i;
3560                 savingComment = FALSE;
3561                 suppressKibitz = 0;
3562                 switch (started) {
3563                   case STARTED_MOVES:
3564                   case STARTED_MOVES_NOHIDE:
3565                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3566                     parse[parse_pos + i - oldi] = NULLCHAR;
3567                     ParseGameHistory(parse);
3568 #if ZIPPY
3569                     if (appData.zippyPlay && first.initDone) {
3570                         FeedMovesToProgram(&first, forwardMostMove);
3571                         if (gameMode == IcsPlayingWhite) {
3572                             if (WhiteOnMove(forwardMostMove)) {
3573                                 if (first.sendTime) {
3574                                   if (first.useColors) {
3575                                     SendToProgram("black\n", &first);
3576                                   }
3577                                   SendTimeRemaining(&first, TRUE);
3578                                 }
3579                                 if (first.useColors) {
3580                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3581                                 }
3582                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3583                                 first.maybeThinking = TRUE;
3584                             } else {
3585                                 if (first.usePlayother) {
3586                                   if (first.sendTime) {
3587                                     SendTimeRemaining(&first, TRUE);
3588                                   }
3589                                   SendToProgram("playother\n", &first);
3590                                   firstMove = FALSE;
3591                                 } else {
3592                                   firstMove = TRUE;
3593                                 }
3594                             }
3595                         } else if (gameMode == IcsPlayingBlack) {
3596                             if (!WhiteOnMove(forwardMostMove)) {
3597                                 if (first.sendTime) {
3598                                   if (first.useColors) {
3599                                     SendToProgram("white\n", &first);
3600                                   }
3601                                   SendTimeRemaining(&first, FALSE);
3602                                 }
3603                                 if (first.useColors) {
3604                                   SendToProgram("black\n", &first);
3605                                 }
3606                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3607                                 first.maybeThinking = TRUE;
3608                             } else {
3609                                 if (first.usePlayother) {
3610                                   if (first.sendTime) {
3611                                     SendTimeRemaining(&first, FALSE);
3612                                   }
3613                                   SendToProgram("playother\n", &first);
3614                                   firstMove = FALSE;
3615                                 } else {
3616                                   firstMove = TRUE;
3617                                 }
3618                             }
3619                         }
3620                     }
3621 #endif
3622                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3623                         /* Moves came from oldmoves or moves command
3624                            while we weren't doing anything else.
3625                            */
3626                         currentMove = forwardMostMove;
3627                         ClearHighlights();/*!!could figure this out*/
3628                         flipView = appData.flipView;
3629                         DrawPosition(TRUE, boards[currentMove]);
3630                         DisplayBothClocks();
3631                         snprintf(str, MSG_SIZ, _("%s vs. %s"),
3632                                 gameInfo.white, gameInfo.black);
3633                         DisplayTitle(str);
3634                         gameMode = IcsIdle;
3635                     } else {
3636                         /* Moves were history of an active game */
3637                         if (gameInfo.resultDetails != NULL) {
3638                             free(gameInfo.resultDetails);
3639                             gameInfo.resultDetails = NULL;
3640                         }
3641                     }
3642                     HistorySet(parseList, backwardMostMove,
3643                                forwardMostMove, currentMove-1);
3644                     DisplayMove(currentMove - 1);
3645                     if (started == STARTED_MOVES) next_out = i;
3646                     started = STARTED_NONE;
3647                     ics_getting_history = H_FALSE;
3648                     break;
3649
3650                   case STARTED_OBSERVE:
3651                     started = STARTED_NONE;
3652                     SendToICS(ics_prefix);
3653                     SendToICS("refresh\n");
3654                     break;
3655
3656                   default:
3657                     break;
3658                 }
3659                 if(bookHit) { // [HGM] book: simulate book reply
3660                     static char bookMove[MSG_SIZ]; // a bit generous?
3661
3662                     programStats.nodes = programStats.depth = programStats.time =
3663                     programStats.score = programStats.got_only_move = 0;
3664                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3665
3666                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3667                     strcat(bookMove, bookHit);
3668                     HandleMachineMove(bookMove, &first);
3669                 }
3670                 continue;
3671             }
3672
3673             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3674                  started == STARTED_HOLDINGS ||
3675                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3676                 /* Accumulate characters in move list or board */
3677                 parse[parse_pos++] = buf[i];
3678             }
3679
3680             /* Start of game messages.  Mostly we detect start of game
3681                when the first board image arrives.  On some versions
3682                of the ICS, though, we need to do a "refresh" after starting
3683                to observe in order to get the current board right away. */
3684             if (looking_at(buf, &i, "Adding game * to observation list")) {
3685                 started = STARTED_OBSERVE;
3686                 continue;
3687             }
3688
3689             /* Handle auto-observe */
3690             if (appData.autoObserve &&
3691                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3692                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3693                 char *player;
3694                 /* Choose the player that was highlighted, if any. */
3695                 if (star_match[0][0] == '\033' ||
3696                     star_match[1][0] != '\033') {
3697                     player = star_match[0];
3698                 } else {
3699                     player = star_match[2];
3700                 }
3701                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3702                         ics_prefix, StripHighlightAndTitle(player));
3703                 SendToICS(str);
3704
3705                 /* Save ratings from notify string */
3706                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3707                 player1Rating = string_to_rating(star_match[1]);
3708                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3709                 player2Rating = string_to_rating(star_match[3]);
3710
3711                 if (appData.debugMode)
3712                   fprintf(debugFP,
3713                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3714                           player1Name, player1Rating,
3715                           player2Name, player2Rating);
3716
3717                 continue;
3718             }
3719
3720             /* Deal with automatic examine mode after a game,
3721                and with IcsObserving -> IcsExamining transition */
3722             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3723                 looking_at(buf, &i, "has made you an examiner of game *")) {
3724
3725                 int gamenum = atoi(star_match[0]);
3726                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3727                     gamenum == ics_gamenum) {
3728                     /* We were already playing or observing this game;
3729                        no need to refetch history */
3730                     gameMode = IcsExamining;
3731                     if (pausing) {
3732                         pauseExamForwardMostMove = forwardMostMove;
3733                     } else if (currentMove < forwardMostMove) {
3734                         ForwardInner(forwardMostMove);
3735                     }
3736                 } else {
3737                     /* I don't think this case really can happen */
3738                     SendToICS(ics_prefix);
3739                     SendToICS("refresh\n");
3740                 }
3741                 continue;
3742             }
3743
3744             /* Error messages */
3745 //          if (ics_user_moved) {
3746             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3747                 if (looking_at(buf, &i, "Illegal move") ||
3748                     looking_at(buf, &i, "Not a legal move") ||
3749                     looking_at(buf, &i, "Your king is in check") ||
3750                     looking_at(buf, &i, "It isn't your turn") ||
3751                     looking_at(buf, &i, "It is not your move")) {
3752                     /* Illegal move */
3753                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3754                         currentMove = forwardMostMove-1;
3755                         DisplayMove(currentMove - 1); /* before DMError */
3756                         DrawPosition(FALSE, boards[currentMove]);
3757                         SwitchClocks(forwardMostMove-1); // [HGM] race
3758                         DisplayBothClocks();
3759                     }
3760                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3761                     ics_user_moved = 0;
3762                     continue;
3763                 }
3764             }
3765
3766             if (looking_at(buf, &i, "still have time") ||
3767                 looking_at(buf, &i, "not out of time") ||
3768                 looking_at(buf, &i, "either player is out of time") ||
3769                 looking_at(buf, &i, "has timeseal; checking")) {
3770                 /* We must have called his flag a little too soon */
3771                 whiteFlag = blackFlag = FALSE;
3772                 continue;
3773             }
3774
3775             if (looking_at(buf, &i, "added * seconds to") ||
3776                 looking_at(buf, &i, "seconds were added to")) {
3777                 /* Update the clocks */
3778                 SendToICS(ics_prefix);
3779                 SendToICS("refresh\n");
3780                 continue;
3781             }
3782
3783             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3784                 ics_clock_paused = TRUE;
3785                 StopClocks();
3786                 continue;
3787             }
3788
3789             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3790                 ics_clock_paused = FALSE;
3791                 StartClocks();
3792                 continue;
3793             }
3794
3795             /* Grab player ratings from the Creating: message.
3796                Note we have to check for the special case when
3797                the ICS inserts things like [white] or [black]. */
3798             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3799                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3800                 /* star_matches:
3801                    0    player 1 name (not necessarily white)
3802                    1    player 1 rating
3803                    2    empty, white, or black (IGNORED)
3804                    3    player 2 name (not necessarily black)
3805                    4    player 2 rating
3806
3807                    The names/ratings are sorted out when the game
3808                    actually starts (below).
3809                 */
3810                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3811                 player1Rating = string_to_rating(star_match[1]);
3812                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3813                 player2Rating = string_to_rating(star_match[4]);
3814
3815                 if (appData.debugMode)
3816                   fprintf(debugFP,
3817                           "Ratings from 'Creating:' %s %d, %s %d\n",
3818                           player1Name, player1Rating,
3819                           player2Name, player2Rating);
3820
3821                 continue;
3822             }
3823
3824             /* Improved generic start/end-of-game messages */
3825             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3826                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3827                 /* If tkind == 0: */
3828                 /* star_match[0] is the game number */
3829                 /*           [1] is the white player's name */
3830                 /*           [2] is the black player's name */
3831                 /* For end-of-game: */
3832                 /*           [3] is the reason for the game end */
3833                 /*           [4] is a PGN end game-token, preceded by " " */
3834                 /* For start-of-game: */
3835                 /*           [3] begins with "Creating" or "Continuing" */
3836                 /*           [4] is " *" or empty (don't care). */
3837                 int gamenum = atoi(star_match[0]);
3838                 char *whitename, *blackname, *why, *endtoken;
3839                 ChessMove endtype = EndOfFile;
3840
3841                 if (tkind == 0) {
3842                   whitename = star_match[1];
3843                   blackname = star_match[2];
3844                   why = star_match[3];
3845                   endtoken = star_match[4];
3846                 } else {
3847                   whitename = star_match[1];
3848                   blackname = star_match[3];
3849                   why = star_match[5];
3850                   endtoken = star_match[6];
3851                 }
3852
3853                 /* Game start messages */
3854                 if (strncmp(why, "Creating ", 9) == 0 ||
3855                     strncmp(why, "Continuing ", 11) == 0) {
3856                     gs_gamenum = gamenum;
3857                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3858                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3859                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3860 #if ZIPPY
3861                     if (appData.zippyPlay) {
3862                         ZippyGameStart(whitename, blackname);
3863                     }
3864 #endif /*ZIPPY*/
3865                     partnerBoardValid = FALSE; // [HGM] bughouse
3866                     continue;
3867                 }
3868
3869                 /* Game end messages */
3870                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3871                     ics_gamenum != gamenum) {
3872                     continue;
3873                 }
3874                 while (endtoken[0] == ' ') endtoken++;
3875                 switch (endtoken[0]) {
3876                   case '*':
3877                   default:
3878                     endtype = GameUnfinished;
3879                     break;
3880                   case '0':
3881                     endtype = BlackWins;
3882                     break;
3883                   case '1':
3884                     if (endtoken[1] == '/')
3885                       endtype = GameIsDrawn;
3886                     else
3887                       endtype = WhiteWins;
3888                     break;
3889                 }
3890                 GameEnds(endtype, why, GE_ICS);
3891 #if ZIPPY
3892                 if (appData.zippyPlay && first.initDone) {
3893                     ZippyGameEnd(endtype, why);
3894                     if (first.pr == NoProc) {
3895                       /* Start the next process early so that we'll
3896                          be ready for the next challenge */
3897                       StartChessProgram(&first);
3898                     }
3899                     /* Send "new" early, in case this command takes
3900                        a long time to finish, so that we'll be ready
3901                        for the next challenge. */
3902                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3903                     Reset(TRUE, TRUE);
3904                 }
3905 #endif /*ZIPPY*/
3906                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3907                 continue;
3908             }
3909
3910             if (looking_at(buf, &i, "Removing game * from observation") ||
3911                 looking_at(buf, &i, "no longer observing game *") ||
3912                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3913                 if (gameMode == IcsObserving &&
3914                     atoi(star_match[0]) == ics_gamenum)
3915                   {
3916                       /* icsEngineAnalyze */
3917                       if (appData.icsEngineAnalyze) {
3918                             ExitAnalyzeMode();
3919                             ModeHighlight();
3920                       }
3921                       StopClocks();
3922                       gameMode = IcsIdle;
3923                       ics_gamenum = -1;
3924                       ics_user_moved = FALSE;
3925                   }
3926                 continue;
3927             }
3928
3929             if (looking_at(buf, &i, "no longer examining game *")) {
3930                 if (gameMode == IcsExamining &&
3931                     atoi(star_match[0]) == ics_gamenum)
3932                   {
3933                       gameMode = IcsIdle;
3934                       ics_gamenum = -1;
3935                       ics_user_moved = FALSE;
3936                   }
3937                 continue;
3938             }
3939
3940             /* Advance leftover_start past any newlines we find,
3941                so only partial lines can get reparsed */
3942             if (looking_at(buf, &i, "\n")) {
3943                 prevColor = curColor;
3944                 if (curColor != ColorNormal) {
3945                     if (oldi > next_out) {
3946                         SendToPlayer(&buf[next_out], oldi - next_out);
3947                         next_out = oldi;
3948                     }
3949                     Colorize(ColorNormal, FALSE);
3950                     curColor = ColorNormal;
3951                 }
3952                 if (started == STARTED_BOARD) {
3953                     started = STARTED_NONE;
3954                     parse[parse_pos] = NULLCHAR;
3955                     ParseBoard12(parse);
3956                     ics_user_moved = 0;
3957
3958                     /* Send premove here */
3959                     if (appData.premove) {
3960                       char str[MSG_SIZ];
3961                       if (currentMove == 0 &&
3962                           gameMode == IcsPlayingWhite &&
3963                           appData.premoveWhite) {
3964                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3965                         if (appData.debugMode)
3966                           fprintf(debugFP, "Sending premove:\n");
3967                         SendToICS(str);
3968                       } else if (currentMove == 1 &&
3969                                  gameMode == IcsPlayingBlack &&
3970                                  appData.premoveBlack) {
3971                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3972                         if (appData.debugMode)
3973                           fprintf(debugFP, "Sending premove:\n");
3974                         SendToICS(str);
3975                       } else if (gotPremove) {
3976                         gotPremove = 0;
3977                         ClearPremoveHighlights();
3978                         if (appData.debugMode)
3979                           fprintf(debugFP, "Sending premove:\n");
3980                           UserMoveEvent(premoveFromX, premoveFromY,
3981                                         premoveToX, premoveToY,
3982                                         premovePromoChar);
3983                       }
3984                     }
3985
3986                     /* Usually suppress following prompt */
3987                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3988                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3989                         if (looking_at(buf, &i, "*% ")) {
3990                             savingComment = FALSE;
3991                             suppressKibitz = 0;
3992                         }
3993                     }
3994                     next_out = i;
3995                 } else if (started == STARTED_HOLDINGS) {
3996                     int gamenum;
3997                     char new_piece[MSG_SIZ];
3998                     started = STARTED_NONE;
3999                     parse[parse_pos] = NULLCHAR;
4000                     if (appData.debugMode)
4001                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4002                                                         parse, currentMove);
4003                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4004                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4005                         if (gameInfo.variant == VariantNormal) {
4006                           /* [HGM] We seem to switch variant during a game!
4007                            * Presumably no holdings were displayed, so we have
4008                            * to move the position two files to the right to
4009                            * create room for them!
4010                            */
4011                           VariantClass newVariant;
4012                           switch(gameInfo.boardWidth) { // base guess on board width
4013                                 case 9:  newVariant = VariantShogi; break;
4014                                 case 10: newVariant = VariantGreat; break;
4015                                 default: newVariant = VariantCrazyhouse; break;
4016                           }
4017                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4018                           /* Get a move list just to see the header, which
4019                              will tell us whether this is really bug or zh */
4020                           if (ics_getting_history == H_FALSE) {
4021                             ics_getting_history = H_REQUESTED;
4022                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4023                             SendToICS(str);
4024                           }
4025                         }
4026                         new_piece[0] = NULLCHAR;
4027                         sscanf(parse, "game %d white [%s black [%s <- %s",
4028                                &gamenum, white_holding, black_holding,
4029                                new_piece);
4030                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4031                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4032                         /* [HGM] copy holdings to board holdings area */
4033                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4034                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4035                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4036 #if ZIPPY
4037                         if (appData.zippyPlay && first.initDone) {
4038                             ZippyHoldings(white_holding, black_holding,
4039                                           new_piece);
4040                         }
4041 #endif /*ZIPPY*/
4042                         if (tinyLayout || smallLayout) {
4043                             char wh[16], bh[16];
4044                             PackHolding(wh, white_holding);
4045                             PackHolding(bh, black_holding);
4046                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4047                                     gameInfo.white, gameInfo.black);
4048                         } else {
4049                           snprintf(str, MSG_SIZ, _("%s [%s] vs. %s [%s]"),
4050                                     gameInfo.white, white_holding,
4051                                     gameInfo.black, black_holding);
4052                         }
4053                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4054                         DrawPosition(FALSE, boards[currentMove]);
4055                         DisplayTitle(str);
4056                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4057                         sscanf(parse, "game %d white [%s black [%s <- %s",
4058                                &gamenum, white_holding, black_holding,
4059                                new_piece);
4060                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4061                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4062                         /* [HGM] copy holdings to partner-board holdings area */
4063                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4064                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4065                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4066                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4067                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4068                       }
4069                     }
4070                     /* Suppress following prompt */
4071                     if (looking_at(buf, &i, "*% ")) {
4072                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4073                         savingComment = FALSE;
4074                         suppressKibitz = 0;
4075                     }
4076                     next_out = i;
4077                 }
4078                 continue;
4079             }
4080
4081             i++;                /* skip unparsed character and loop back */
4082         }
4083
4084         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4085 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4086 //          SendToPlayer(&buf[next_out], i - next_out);
4087             started != STARTED_HOLDINGS && leftover_start > next_out) {
4088             SendToPlayer(&buf[next_out], leftover_start - next_out);
4089             next_out = i;
4090         }
4091
4092         leftover_len = buf_len - leftover_start;
4093         /* if buffer ends with something we couldn't parse,
4094            reparse it after appending the next read */
4095
4096     } else if (count == 0) {
4097         RemoveInputSource(isr);
4098         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4099     } else {
4100         DisplayFatalError(_("Error reading from ICS"), error, 1);
4101     }
4102 }
4103
4104
4105 /* Board style 12 looks like this:
4106
4107    <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
4108
4109  * The "<12> " is stripped before it gets to this routine.  The two
4110  * trailing 0's (flip state and clock ticking) are later addition, and
4111  * some chess servers may not have them, or may have only the first.
4112  * Additional trailing fields may be added in the future.
4113  */
4114
4115 #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"
4116
4117 #define RELATION_OBSERVING_PLAYED    0
4118 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4119 #define RELATION_PLAYING_MYMOVE      1
4120 #define RELATION_PLAYING_NOTMYMOVE  -1
4121 #define RELATION_EXAMINING           2
4122 #define RELATION_ISOLATED_BOARD     -3
4123 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4124
4125 void
4126 ParseBoard12(string)
4127      char *string;
4128 {
4129     GameMode newGameMode;
4130     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4131     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4132     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4133     char to_play, board_chars[200];
4134     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4135     char black[32], white[32];
4136     Board board;
4137     int prevMove = currentMove;
4138     int ticking = 2;
4139     ChessMove moveType;
4140     int fromX, fromY, toX, toY;
4141     char promoChar;
4142     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4143     char *bookHit = NULL; // [HGM] book
4144     Boolean weird = FALSE, reqFlag = FALSE;
4145
4146     fromX = fromY = toX = toY = -1;
4147
4148     newGame = FALSE;
4149
4150     if (appData.debugMode)
4151       fprintf(debugFP, _("Parsing board: %s\n"), string);
4152
4153     move_str[0] = NULLCHAR;
4154     elapsed_time[0] = NULLCHAR;
4155     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4156         int  i = 0, j;
4157         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4158             if(string[i] == ' ') { ranks++; files = 0; }
4159             else files++;
4160             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4161             i++;
4162         }
4163         for(j = 0; j <i; j++) board_chars[j] = string[j];
4164         board_chars[i] = '\0';
4165         string += i + 1;
4166     }
4167     n = sscanf(string, PATTERN, &to_play, &double_push,
4168                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4169                &gamenum, white, black, &relation, &basetime, &increment,
4170                &white_stren, &black_stren, &white_time, &black_time,
4171                &moveNum, str, elapsed_time, move_str, &ics_flip,
4172                &ticking);
4173
4174     if (n < 21) {
4175         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4176         DisplayError(str, 0);
4177         return;
4178     }
4179
4180     /* Convert the move number to internal form */
4181     moveNum = (moveNum - 1) * 2;
4182     if (to_play == 'B') moveNum++;
4183     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4184       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4185                         0, 1);
4186       return;
4187     }
4188
4189     switch (relation) {
4190       case RELATION_OBSERVING_PLAYED:
4191       case RELATION_OBSERVING_STATIC:
4192         if (gamenum == -1) {
4193             /* Old ICC buglet */
4194             relation = RELATION_OBSERVING_STATIC;
4195         }
4196         newGameMode = IcsObserving;
4197         break;
4198       case RELATION_PLAYING_MYMOVE:
4199       case RELATION_PLAYING_NOTMYMOVE:
4200         newGameMode =
4201           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4202             IcsPlayingWhite : IcsPlayingBlack;
4203         break;
4204       case RELATION_EXAMINING:
4205         newGameMode = IcsExamining;
4206         break;
4207       case RELATION_ISOLATED_BOARD:
4208       default:
4209         /* Just display this board.  If user was doing something else,
4210            we will forget about it until the next board comes. */
4211         newGameMode = IcsIdle;
4212         break;
4213       case RELATION_STARTING_POSITION:
4214         newGameMode = gameMode;
4215         break;
4216     }
4217
4218     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4219          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4220       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4221       char *toSqr;
4222       for (k = 0; k < ranks; k++) {
4223         for (j = 0; j < files; j++)
4224           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4225         if(gameInfo.holdingsWidth > 1) {
4226              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4227              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4228         }
4229       }
4230       CopyBoard(partnerBoard, board);
4231       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4232         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4233         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4234       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4235       if(toSqr = strchr(str, '-')) {
4236         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4237         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4238       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4239       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4240       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4241       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4242       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4243       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4244                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4245       DisplayMessage(partnerStatus, "");
4246         partnerBoardValid = TRUE;
4247       return;
4248     }
4249
4250     /* Modify behavior for initial board display on move listing
4251        of wild games.
4252        */
4253     switch (ics_getting_history) {
4254       case H_FALSE:
4255       case H_REQUESTED:
4256         break;
4257       case H_GOT_REQ_HEADER:
4258       case H_GOT_UNREQ_HEADER:
4259         /* This is the initial position of the current game */
4260         gamenum = ics_gamenum;
4261         moveNum = 0;            /* old ICS bug workaround */
4262         if (to_play == 'B') {
4263           startedFromSetupPosition = TRUE;
4264           blackPlaysFirst = TRUE;
4265           moveNum = 1;
4266           if (forwardMostMove == 0) forwardMostMove = 1;
4267           if (backwardMostMove == 0) backwardMostMove = 1;
4268           if (currentMove == 0) currentMove = 1;
4269         }
4270         newGameMode = gameMode;
4271         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4272         break;
4273       case H_GOT_UNWANTED_HEADER:
4274         /* This is an initial board that we don't want */
4275         return;
4276       case H_GETTING_MOVES:
4277         /* Should not happen */
4278         DisplayError(_("Error gathering move list: extra board"), 0);
4279         ics_getting_history = H_FALSE;
4280         return;
4281     }
4282
4283    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4284                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4285      /* [HGM] We seem to have switched variant unexpectedly
4286       * Try to guess new variant from board size
4287       */
4288           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4289           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4290           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4291           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4292           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4293           if(!weird) newVariant = VariantNormal;
4294           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4295           /* Get a move list just to see the header, which
4296              will tell us whether this is really bug or zh */
4297           if (ics_getting_history == H_FALSE) {
4298             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4299             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4300             SendToICS(str);
4301           }
4302     }
4303
4304     /* Take action if this is the first board of a new game, or of a
4305        different game than is currently being displayed.  */
4306     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4307         relation == RELATION_ISOLATED_BOARD) {
4308
4309         /* Forget the old game and get the history (if any) of the new one */
4310         if (gameMode != BeginningOfGame) {
4311           Reset(TRUE, TRUE);
4312         }
4313         newGame = TRUE;
4314         if (appData.autoRaiseBoard) BoardToTop();
4315         prevMove = -3;
4316         if (gamenum == -1) {
4317             newGameMode = IcsIdle;
4318         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4319                    appData.getMoveList && !reqFlag) {
4320             /* Need to get game history */
4321             ics_getting_history = H_REQUESTED;
4322             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4323             SendToICS(str);
4324         }
4325
4326         /* Initially flip the board to have black on the bottom if playing
4327            black or if the ICS flip flag is set, but let the user change
4328            it with the Flip View button. */
4329         flipView = appData.autoFlipView ?
4330           (newGameMode == IcsPlayingBlack) || ics_flip :
4331           appData.flipView;
4332
4333         /* Done with values from previous mode; copy in new ones */
4334         gameMode = newGameMode;
4335         ModeHighlight();
4336         ics_gamenum = gamenum;
4337         if (gamenum == gs_gamenum) {
4338             int klen = strlen(gs_kind);
4339             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4340             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4341             gameInfo.event = StrSave(str);
4342         } else {
4343             gameInfo.event = StrSave("ICS game");
4344         }
4345         gameInfo.site = StrSave(appData.icsHost);
4346         gameInfo.date = PGNDate();
4347         gameInfo.round = StrSave("-");
4348         gameInfo.white = StrSave(white);
4349         gameInfo.black = StrSave(black);
4350         timeControl = basetime * 60 * 1000;
4351         timeControl_2 = 0;
4352         timeIncrement = increment * 1000;
4353         movesPerSession = 0;
4354         gameInfo.timeControl = TimeControlTagValue();
4355         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4356   if (appData.debugMode) {
4357     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4358     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4359     setbuf(debugFP, NULL);
4360   }
4361
4362         gameInfo.outOfBook = NULL;
4363
4364         /* Do we have the ratings? */
4365         if (strcmp(player1Name, white) == 0 &&
4366             strcmp(player2Name, black) == 0) {
4367             if (appData.debugMode)
4368               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4369                       player1Rating, player2Rating);
4370             gameInfo.whiteRating = player1Rating;
4371             gameInfo.blackRating = player2Rating;
4372         } else if (strcmp(player2Name, white) == 0 &&
4373                    strcmp(player1Name, black) == 0) {
4374             if (appData.debugMode)
4375               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4376                       player2Rating, player1Rating);
4377             gameInfo.whiteRating = player2Rating;
4378             gameInfo.blackRating = player1Rating;
4379         }
4380         player1Name[0] = player2Name[0] = NULLCHAR;
4381
4382         /* Silence shouts if requested */
4383         if (appData.quietPlay &&
4384             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4385             SendToICS(ics_prefix);
4386             SendToICS("set shout 0\n");
4387         }
4388     }
4389
4390     /* Deal with midgame name changes */
4391     if (!newGame) {
4392         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4393             if (gameInfo.white) free(gameInfo.white);
4394             gameInfo.white = StrSave(white);
4395         }
4396         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4397             if (gameInfo.black) free(gameInfo.black);
4398             gameInfo.black = StrSave(black);
4399         }
4400     }
4401
4402     /* Throw away game result if anything actually changes in examine mode */
4403     if (gameMode == IcsExamining && !newGame) {
4404         gameInfo.result = GameUnfinished;
4405         if (gameInfo.resultDetails != NULL) {
4406             free(gameInfo.resultDetails);
4407             gameInfo.resultDetails = NULL;
4408         }
4409     }
4410
4411     /* In pausing && IcsExamining mode, we ignore boards coming
4412        in if they are in a different variation than we are. */
4413     if (pauseExamInvalid) return;
4414     if (pausing && gameMode == IcsExamining) {
4415         if (moveNum <= pauseExamForwardMostMove) {
4416             pauseExamInvalid = TRUE;
4417             forwardMostMove = pauseExamForwardMostMove;
4418             return;
4419         }
4420     }
4421
4422   if (appData.debugMode) {
4423     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4424   }
4425     /* Parse the board */
4426     for (k = 0; k < ranks; k++) {
4427       for (j = 0; j < files; j++)
4428         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4429       if(gameInfo.holdingsWidth > 1) {
4430            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4431            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4432       }
4433     }
4434     CopyBoard(boards[moveNum], board);
4435     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4436     if (moveNum == 0) {
4437         startedFromSetupPosition =
4438           !CompareBoards(board, initialPosition);
4439         if(startedFromSetupPosition)
4440             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4441     }
4442
4443     /* [HGM] Set castling rights. Take the outermost Rooks,
4444        to make it also work for FRC opening positions. Note that board12
4445        is really defective for later FRC positions, as it has no way to
4446        indicate which Rook can castle if they are on the same side of King.
4447        For the initial position we grant rights to the outermost Rooks,
4448        and remember thos rights, and we then copy them on positions
4449        later in an FRC game. This means WB might not recognize castlings with
4450        Rooks that have moved back to their original position as illegal,
4451        but in ICS mode that is not its job anyway.
4452     */
4453     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4454     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4455
4456         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4457             if(board[0][i] == WhiteRook) j = i;
4458         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4459         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4460             if(board[0][i] == WhiteRook) j = i;
4461         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4462         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4463             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4464         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4465         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4466             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4467         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4468
4469         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4470         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4471             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4472         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4473             if(board[BOARD_HEIGHT-1][k] == bKing)
4474                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4475         if(gameInfo.variant == VariantTwoKings) {
4476             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4477             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4478             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4479         }
4480     } else { int r;
4481         r = boards[moveNum][CASTLING][0] = initialRights[0];
4482         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4483         r = boards[moveNum][CASTLING][1] = initialRights[1];
4484         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4485         r = boards[moveNum][CASTLING][3] = initialRights[3];
4486         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4487         r = boards[moveNum][CASTLING][4] = initialRights[4];
4488         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4489         /* wildcastle kludge: always assume King has rights */
4490         r = boards[moveNum][CASTLING][2] = initialRights[2];
4491         r = boards[moveNum][CASTLING][5] = initialRights[5];
4492     }
4493     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4494     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4495
4496
4497     if (ics_getting_history == H_GOT_REQ_HEADER ||
4498         ics_getting_history == H_GOT_UNREQ_HEADER) {
4499         /* This was an initial position from a move list, not
4500            the current position */
4501         return;
4502     }
4503
4504     /* Update currentMove and known move number limits */
4505     newMove = newGame || moveNum > forwardMostMove;
4506
4507     if (newGame) {
4508         forwardMostMove = backwardMostMove = currentMove = moveNum;
4509         if (gameMode == IcsExamining && moveNum == 0) {
4510           /* Workaround for ICS limitation: we are not told the wild
4511              type when starting to examine a game.  But if we ask for
4512              the move list, the move list header will tell us */
4513             ics_getting_history = H_REQUESTED;
4514             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4515             SendToICS(str);
4516         }
4517     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4518                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4519 #if ZIPPY
4520         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4521         /* [HGM] applied this also to an engine that is silently watching        */
4522         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4523             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4524             gameInfo.variant == currentlyInitializedVariant) {
4525           takeback = forwardMostMove - moveNum;
4526           for (i = 0; i < takeback; i++) {
4527             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4528             SendToProgram("undo\n", &first);
4529           }
4530         }
4531 #endif
4532
4533         forwardMostMove = moveNum;
4534         if (!pausing || currentMove > forwardMostMove)
4535           currentMove = forwardMostMove;
4536     } else {
4537         /* New part of history that is not contiguous with old part */
4538         if (pausing && gameMode == IcsExamining) {
4539             pauseExamInvalid = TRUE;
4540             forwardMostMove = pauseExamForwardMostMove;
4541             return;
4542         }
4543         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4544 #if ZIPPY
4545             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4546                 // [HGM] when we will receive the move list we now request, it will be
4547                 // fed to the engine from the first move on. So if the engine is not
4548                 // in the initial position now, bring it there.
4549                 InitChessProgram(&first, 0);
4550             }
4551 #endif
4552             ics_getting_history = H_REQUESTED;
4553             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4554             SendToICS(str);
4555         }
4556         forwardMostMove = backwardMostMove = currentMove = moveNum;
4557     }
4558
4559     /* Update the clocks */
4560     if (strchr(elapsed_time, '.')) {
4561       /* Time is in ms */
4562       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4563       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4564     } else {
4565       /* Time is in seconds */
4566       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4567       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4568     }
4569
4570
4571 #if ZIPPY
4572     if (appData.zippyPlay && newGame &&
4573         gameMode != IcsObserving && gameMode != IcsIdle &&
4574         gameMode != IcsExamining)
4575       ZippyFirstBoard(moveNum, basetime, increment);
4576 #endif
4577
4578     /* Put the move on the move list, first converting
4579        to canonical algebraic form. */
4580     if (moveNum > 0) {
4581   if (appData.debugMode) {
4582     if (appData.debugMode) { int f = forwardMostMove;
4583         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4584                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4585                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4586     }
4587     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4588     fprintf(debugFP, "moveNum = %d\n", moveNum);
4589     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4590     setbuf(debugFP, NULL);
4591   }
4592         if (moveNum <= backwardMostMove) {
4593             /* We don't know what the board looked like before
4594                this move.  Punt. */
4595           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4596             strcat(parseList[moveNum - 1], " ");
4597             strcat(parseList[moveNum - 1], elapsed_time);
4598             moveList[moveNum - 1][0] = NULLCHAR;
4599         } else if (strcmp(move_str, "none") == 0) {
4600             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4601             /* Again, we don't know what the board looked like;
4602                this is really the start of the game. */
4603             parseList[moveNum - 1][0] = NULLCHAR;
4604             moveList[moveNum - 1][0] = NULLCHAR;
4605             backwardMostMove = moveNum;
4606             startedFromSetupPosition = TRUE;
4607             fromX = fromY = toX = toY = -1;
4608         } else {
4609           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4610           //                 So we parse the long-algebraic move string in stead of the SAN move
4611           int valid; char buf[MSG_SIZ], *prom;
4612
4613           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4614                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4615           // str looks something like "Q/a1-a2"; kill the slash
4616           if(str[1] == '/')
4617             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4618           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4619           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4620                 strcat(buf, prom); // long move lacks promo specification!
4621           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4622                 if(appData.debugMode)
4623                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4624                 safeStrCpy(move_str, buf, MSG_SIZ);
4625           }
4626           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4627                                 &fromX, &fromY, &toX, &toY, &promoChar)
4628                || ParseOneMove(buf, moveNum - 1, &moveType,
4629                                 &fromX, &fromY, &toX, &toY, &promoChar);
4630           // end of long SAN patch
4631           if (valid) {
4632             (void) CoordsToAlgebraic(boards[moveNum - 1],
4633                                      PosFlags(moveNum - 1),
4634                                      fromY, fromX, toY, toX, promoChar,
4635                                      parseList[moveNum-1]);
4636             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4637               case MT_NONE:
4638               case MT_STALEMATE:
4639               default:
4640                 break;
4641               case MT_CHECK:
4642                 if(gameInfo.variant != VariantShogi)
4643                     strcat(parseList[moveNum - 1], "+");
4644                 break;
4645               case MT_CHECKMATE:
4646               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4647                 strcat(parseList[moveNum - 1], "#");
4648                 break;
4649             }
4650             strcat(parseList[moveNum - 1], " ");
4651             strcat(parseList[moveNum - 1], elapsed_time);
4652             /* currentMoveString is set as a side-effect of ParseOneMove */
4653             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4654             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4655             strcat(moveList[moveNum - 1], "\n");
4656
4657             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4658                                  && gameInfo.variant != VariantGrand) // inherit info that ICS does not give from previous board
4659               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4660                 ChessSquare old, new = boards[moveNum][k][j];
4661                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4662                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4663                   if(old == new) continue;
4664                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4665                   else if(new == WhiteWazir || new == BlackWazir) {
4666                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4667                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4668                       else boards[moveNum][k][j] = old; // preserve type of Gold
4669                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4670                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4671               }
4672           } else {
4673             /* Move from ICS was illegal!?  Punt. */
4674             if (appData.debugMode) {
4675               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4676               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4677             }
4678             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4679             strcat(parseList[moveNum - 1], " ");
4680             strcat(parseList[moveNum - 1], elapsed_time);
4681             moveList[moveNum - 1][0] = NULLCHAR;
4682             fromX = fromY = toX = toY = -1;
4683           }
4684         }
4685   if (appData.debugMode) {
4686     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4687     setbuf(debugFP, NULL);
4688   }
4689
4690 #if ZIPPY
4691         /* Send move to chess program (BEFORE animating it). */
4692         if (appData.zippyPlay && !newGame && newMove &&
4693            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4694
4695             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4696                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4697                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4698                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4699                             move_str);
4700                     DisplayError(str, 0);
4701                 } else {
4702                     if (first.sendTime) {
4703                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4704                     }
4705                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4706                     if (firstMove && !bookHit) {
4707                         firstMove = FALSE;
4708                         if (first.useColors) {
4709                           SendToProgram(gameMode == IcsPlayingWhite ?
4710                                         "white\ngo\n" :
4711                                         "black\ngo\n", &first);
4712                         } else {
4713                           SendToProgram("go\n", &first);
4714                         }
4715                         first.maybeThinking = TRUE;
4716                     }
4717                 }
4718             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4719               if (moveList[moveNum - 1][0] == NULLCHAR) {
4720                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4721                 DisplayError(str, 0);
4722               } else {
4723                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4724                 SendMoveToProgram(moveNum - 1, &first);
4725               }
4726             }
4727         }
4728 #endif
4729     }
4730
4731     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4732         /* If move comes from a remote source, animate it.  If it
4733            isn't remote, it will have already been animated. */
4734         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4735             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4736         }
4737         if (!pausing && appData.highlightLastMove) {
4738             SetHighlights(fromX, fromY, toX, toY);
4739         }
4740     }
4741
4742     /* Start the clocks */
4743     whiteFlag = blackFlag = FALSE;
4744     appData.clockMode = !(basetime == 0 && increment == 0);
4745     if (ticking == 0) {
4746       ics_clock_paused = TRUE;
4747       StopClocks();
4748     } else if (ticking == 1) {
4749       ics_clock_paused = FALSE;
4750     }
4751     if (gameMode == IcsIdle ||
4752         relation == RELATION_OBSERVING_STATIC ||
4753         relation == RELATION_EXAMINING ||
4754         ics_clock_paused)
4755       DisplayBothClocks();
4756     else
4757       StartClocks();
4758
4759     /* Display opponents and material strengths */
4760     if (gameInfo.variant != VariantBughouse &&
4761         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4762         if (tinyLayout || smallLayout) {
4763             if(gameInfo.variant == VariantNormal)
4764               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4765                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4766                     basetime, increment);
4767             else
4768               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4769                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4770                     basetime, increment, (int) gameInfo.variant);
4771         } else {
4772             if(gameInfo.variant == VariantNormal)
4773               snprintf(str, MSG_SIZ, _("%s (%d) vs. %s (%d) {%d %d}"),
4774                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4775                     basetime, increment);
4776             else
4777               snprintf(str, MSG_SIZ, _("%s (%d) vs. %s (%d) {%d %d %s}"),
4778                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4779                     basetime, increment, VariantName(gameInfo.variant));
4780         }
4781         DisplayTitle(str);
4782   if (appData.debugMode) {
4783     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4784   }
4785     }
4786
4787
4788     /* Display the board */
4789     if (!pausing && !appData.noGUI) {
4790
4791       if (appData.premove)
4792           if (!gotPremove ||
4793              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4794              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4795               ClearPremoveHighlights();
4796
4797       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4798         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4799       DrawPosition(j, boards[currentMove]);
4800
4801       DisplayMove(moveNum - 1);
4802       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4803             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4804               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4805         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4806       }
4807     }
4808
4809     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4810 #if ZIPPY
4811     if(bookHit) { // [HGM] book: simulate book reply
4812         static char bookMove[MSG_SIZ]; // a bit generous?
4813
4814         programStats.nodes = programStats.depth = programStats.time =
4815         programStats.score = programStats.got_only_move = 0;
4816         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4817
4818         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4819         strcat(bookMove, bookHit);
4820         HandleMachineMove(bookMove, &first);
4821     }
4822 #endif
4823 }
4824
4825 void
4826 GetMoveListEvent()
4827 {
4828     char buf[MSG_SIZ];
4829     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4830         ics_getting_history = H_REQUESTED;
4831         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4832         SendToICS(buf);
4833     }
4834 }
4835
4836 void
4837 AnalysisPeriodicEvent(force)
4838      int force;
4839 {
4840     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4841          && !force) || !appData.periodicUpdates)
4842       return;
4843
4844     /* Send . command to Crafty to collect stats */
4845     SendToProgram(".\n", &first);
4846
4847     /* Don't send another until we get a response (this makes
4848        us stop sending to old Crafty's which don't understand
4849        the "." command (sending illegal cmds resets node count & time,
4850        which looks bad)) */
4851     programStats.ok_to_send = 0;
4852 }
4853
4854 void ics_update_width(new_width)
4855         int new_width;
4856 {
4857         ics_printf("set width %d\n", new_width);
4858 }
4859
4860 void
4861 SendMoveToProgram(moveNum, cps)
4862      int moveNum;
4863      ChessProgramState *cps;
4864 {
4865     char buf[MSG_SIZ];
4866
4867     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4868         // null move in variant where engine does not understand it (for analysis purposes)
4869         SendBoard(cps, moveNum + 1); // send position after move in stead.
4870         return;
4871     }
4872     if (cps->useUsermove) {
4873       SendToProgram("usermove ", cps);
4874     }
4875     if (cps->useSAN) {
4876       char *space;
4877       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4878         int len = space - parseList[moveNum];
4879         memcpy(buf, parseList[moveNum], len);
4880         buf[len++] = '\n';
4881         buf[len] = NULLCHAR;
4882       } else {
4883         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4884       }
4885       SendToProgram(buf, cps);
4886     } else {
4887       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4888         AlphaRank(moveList[moveNum], 4);
4889         SendToProgram(moveList[moveNum], cps);
4890         AlphaRank(moveList[moveNum], 4); // and back
4891       } else
4892       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4893        * the engine. It would be nice to have a better way to identify castle
4894        * moves here. */
4895       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4896                                                                          && cps->useOOCastle) {
4897         int fromX = moveList[moveNum][0] - AAA;
4898         int fromY = moveList[moveNum][1] - ONE;
4899         int toX = moveList[moveNum][2] - AAA;
4900         int toY = moveList[moveNum][3] - ONE;
4901         if((boards[moveNum][fromY][fromX] == WhiteKing
4902             && boards[moveNum][toY][toX] == WhiteRook)
4903            || (boards[moveNum][fromY][fromX] == BlackKing
4904                && boards[moveNum][toY][toX] == BlackRook)) {
4905           if(toX > fromX) SendToProgram("O-O\n", cps);
4906           else SendToProgram("O-O-O\n", cps);
4907         }
4908         else SendToProgram(moveList[moveNum], cps);
4909       } else
4910       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4911         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4912           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4913           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4914                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4915         } else
4916           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4917                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4918         SendToProgram(buf, cps);
4919       }
4920       else SendToProgram(moveList[moveNum], cps);
4921       /* End of additions by Tord */
4922     }
4923
4924     /* [HGM] setting up the opening has brought engine in force mode! */
4925     /*       Send 'go' if we are in a mode where machine should play. */
4926     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4927         (gameMode == TwoMachinesPlay   ||
4928 #if ZIPPY
4929          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4930 #endif
4931          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4932         SendToProgram("go\n", cps);
4933   if (appData.debugMode) {
4934     fprintf(debugFP, "(extra)\n");
4935   }
4936     }
4937     setboardSpoiledMachineBlack = 0;
4938 }
4939
4940 void
4941 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4942      ChessMove moveType;
4943      int fromX, fromY, toX, toY;
4944      char promoChar;
4945 {
4946     char user_move[MSG_SIZ];
4947     char suffix[4];
4948
4949     if(gameInfo.variant == VariantSChess && promoChar) {
4950         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4951         if(toX == BOARD_WIDTH>>1) moveType = WhitePromotion; // kludge to do gating at Rook
4952     } else suffix[0] = NULLCHAR;
4953
4954     switch (moveType) {
4955       default:
4956         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4957                 (int)moveType, fromX, fromY, toX, toY);
4958         DisplayError(user_move + strlen("say "), 0);
4959         break;
4960       case WhiteKingSideCastle:
4961       case BlackKingSideCastle:
4962       case WhiteQueenSideCastleWild:
4963       case BlackQueenSideCastleWild:
4964       /* PUSH Fabien */
4965       case WhiteHSideCastleFR:
4966       case BlackHSideCastleFR:
4967       /* POP Fabien */
4968         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4969         break;
4970       case WhiteQueenSideCastle:
4971       case BlackQueenSideCastle:
4972       case WhiteKingSideCastleWild:
4973       case BlackKingSideCastleWild:
4974       /* PUSH Fabien */
4975       case WhiteASideCastleFR:
4976       case BlackASideCastleFR:
4977       /* POP Fabien */
4978         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
4979         break;
4980       case WhiteNonPromotion:
4981       case BlackNonPromotion:
4982         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4983         break;
4984       case WhitePromotion:
4985       case BlackPromotion:
4986         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4987           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4988                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4989                 PieceToChar(WhiteFerz));
4990         else if(gameInfo.variant == VariantGreat)
4991           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4992                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4993                 PieceToChar(WhiteMan));
4994         else
4995           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4996                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4997                 promoChar);
4998         break;
4999       case WhiteDrop:
5000       case BlackDrop:
5001       drop:
5002         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5003                  ToUpper(PieceToChar((ChessSquare) fromX)),
5004                  AAA + toX, ONE + toY);
5005         break;
5006       case IllegalMove:  /* could be a variant we don't quite understand */
5007         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5008       case NormalMove:
5009       case WhiteCapturesEnPassant:
5010       case BlackCapturesEnPassant:
5011         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5012                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5013         break;
5014     }
5015     SendToICS(user_move);
5016     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5017         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5018 }
5019
5020 void
5021 UploadGameEvent()
5022 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5023     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5024     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5025     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5026       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5027       return;
5028     }
5029     if(gameMode != IcsExamining) { // is this ever not the case?
5030         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5031
5032         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5033           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5034         } else { // on FICS we must first go to general examine mode
5035           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5036         }
5037         if(gameInfo.variant != VariantNormal) {
5038             // try figure out wild number, as xboard names are not always valid on ICS
5039             for(i=1; i<=36; i++) {
5040               snprintf(buf, MSG_SIZ, "wild/%d", i);
5041                 if(StringToVariant(buf) == gameInfo.variant) break;
5042             }
5043             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5044             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5045             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5046         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5047         SendToICS(ics_prefix);
5048         SendToICS(buf);
5049         if(startedFromSetupPosition || backwardMostMove != 0) {
5050           fen = PositionToFEN(backwardMostMove, NULL);
5051           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5052             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5053             SendToICS(buf);
5054           } else { // FICS: everything has to set by separate bsetup commands
5055             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5056             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5057             SendToICS(buf);
5058             if(!WhiteOnMove(backwardMostMove)) {
5059                 SendToICS("bsetup tomove black\n");
5060             }
5061             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5062             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5063             SendToICS(buf);
5064             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5065             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5066             SendToICS(buf);
5067             i = boards[backwardMostMove][EP_STATUS];
5068             if(i >= 0) { // set e.p.
5069               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5070                 SendToICS(buf);
5071             }
5072             bsetup++;
5073           }
5074         }
5075       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5076             SendToICS("bsetup done\n"); // switch to normal examining.
5077     }
5078     for(i = backwardMostMove; i<last; i++) {
5079         char buf[20];
5080         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5081         SendToICS(buf);
5082     }
5083     SendToICS(ics_prefix);
5084     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5085 }
5086
5087 void
5088 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5089      int rf, ff, rt, ft;
5090      char promoChar;
5091      char move[7];
5092 {
5093     if (rf == DROP_RANK) {
5094       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5095       sprintf(move, "%c@%c%c\n",
5096                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5097     } else {
5098         if (promoChar == 'x' || promoChar == NULLCHAR) {
5099           sprintf(move, "%c%c%c%c\n",
5100                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5101         } else {
5102             sprintf(move, "%c%c%c%c%c\n",
5103                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5104         }
5105     }
5106 }
5107
5108 void
5109 ProcessICSInitScript(f)
5110      FILE *f;
5111 {
5112     char buf[MSG_SIZ];
5113
5114     while (fgets(buf, MSG_SIZ, f)) {
5115         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5116     }
5117
5118     fclose(f);
5119 }
5120
5121
5122 static int lastX, lastY, selectFlag, dragging;
5123
5124 void
5125 Sweep(int step)
5126 {
5127     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5128     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5129     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5130     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5131     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5132     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5133     do {
5134         promoSweep -= step;
5135         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5136         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5137         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5138         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5139         if(!step) step = -1;
5140     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5141             appData.testLegality && (promoSweep == king ||
5142             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5143     ChangeDragPiece(promoSweep);
5144 }
5145
5146 int PromoScroll(int x, int y)
5147 {
5148   int step = 0;
5149
5150   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5151   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5152   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5153   if(!step) return FALSE;
5154   lastX = x; lastY = y;
5155   if((promoSweep < BlackPawn) == flipView) step = -step;
5156   if(step > 0) selectFlag = 1;
5157   if(!selectFlag) Sweep(step);
5158   return FALSE;
5159 }
5160
5161 void
5162 NextPiece(int step)
5163 {
5164     ChessSquare piece = boards[currentMove][toY][toX];
5165     do {
5166         pieceSweep -= step;
5167         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5168         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5169         if(!step) step = -1;
5170     } while(PieceToChar(pieceSweep) == '.');
5171     boards[currentMove][toY][toX] = pieceSweep;
5172     DrawPosition(FALSE, boards[currentMove]);
5173     boards[currentMove][toY][toX] = piece;
5174 }
5175 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5176 void
5177 AlphaRank(char *move, int n)
5178 {
5179 //    char *p = move, c; int x, y;
5180
5181     if (appData.debugMode) {
5182         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5183     }
5184
5185     if(move[1]=='*' &&
5186        move[2]>='0' && move[2]<='9' &&
5187        move[3]>='a' && move[3]<='x'    ) {
5188         move[1] = '@';
5189         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5190         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5191     } else
5192     if(move[0]>='0' && move[0]<='9' &&
5193        move[1]>='a' && move[1]<='x' &&
5194        move[2]>='0' && move[2]<='9' &&
5195        move[3]>='a' && move[3]<='x'    ) {
5196         /* input move, Shogi -> normal */
5197         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5198         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5199         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5200         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5201     } else
5202     if(move[1]=='@' &&
5203        move[3]>='0' && move[3]<='9' &&
5204        move[2]>='a' && move[2]<='x'    ) {
5205         move[1] = '*';
5206         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5207         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5208     } else
5209     if(
5210        move[0]>='a' && move[0]<='x' &&
5211        move[3]>='0' && move[3]<='9' &&
5212        move[2]>='a' && move[2]<='x'    ) {
5213          /* output move, normal -> Shogi */
5214         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5215         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5216         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5217         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5218         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5219     }
5220     if (appData.debugMode) {
5221         fprintf(debugFP, "   out = '%s'\n", move);
5222     }
5223 }
5224
5225 char yy_textstr[8000];
5226
5227 /* Parser for moves from gnuchess, ICS, or user typein box */
5228 Boolean
5229 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5230      char *move;
5231      int moveNum;
5232      ChessMove *moveType;
5233      int *fromX, *fromY, *toX, *toY;
5234      char *promoChar;
5235 {
5236     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5237
5238     switch (*moveType) {
5239       case WhitePromotion:
5240       case BlackPromotion:
5241       case WhiteNonPromotion:
5242       case BlackNonPromotion:
5243       case NormalMove:
5244       case WhiteCapturesEnPassant:
5245       case BlackCapturesEnPassant:
5246       case WhiteKingSideCastle:
5247       case WhiteQueenSideCastle:
5248       case BlackKingSideCastle:
5249       case BlackQueenSideCastle:
5250       case WhiteKingSideCastleWild:
5251       case WhiteQueenSideCastleWild:
5252       case BlackKingSideCastleWild:
5253       case BlackQueenSideCastleWild:
5254       /* Code added by Tord: */
5255       case WhiteHSideCastleFR:
5256       case WhiteASideCastleFR:
5257       case BlackHSideCastleFR:
5258       case BlackASideCastleFR:
5259       /* End of code added by Tord */
5260       case IllegalMove:         /* bug or odd chess variant */
5261         *fromX = currentMoveString[0] - AAA;
5262         *fromY = currentMoveString[1] - ONE;
5263         *toX = currentMoveString[2] - AAA;
5264         *toY = currentMoveString[3] - ONE;
5265         *promoChar = currentMoveString[4];
5266         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5267             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5268     if (appData.debugMode) {
5269         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5270     }
5271             *fromX = *fromY = *toX = *toY = 0;
5272             return FALSE;
5273         }
5274         if (appData.testLegality) {
5275           return (*moveType != IllegalMove);
5276         } else {
5277           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5278                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5279         }
5280
5281       case WhiteDrop:
5282       case BlackDrop:
5283         *fromX = *moveType == WhiteDrop ?
5284           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5285           (int) CharToPiece(ToLower(currentMoveString[0]));
5286         *fromY = DROP_RANK;
5287         *toX = currentMoveString[2] - AAA;
5288         *toY = currentMoveString[3] - ONE;
5289         *promoChar = NULLCHAR;
5290         return TRUE;
5291
5292       case AmbiguousMove:
5293       case ImpossibleMove:
5294       case EndOfFile:
5295       case ElapsedTime:
5296       case Comment:
5297       case PGNTag:
5298       case NAG:
5299       case WhiteWins:
5300       case BlackWins:
5301       case GameIsDrawn:
5302       default:
5303     if (appData.debugMode) {
5304         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5305     }
5306         /* bug? */
5307         *fromX = *fromY = *toX = *toY = 0;
5308         *promoChar = NULLCHAR;
5309         return FALSE;
5310     }
5311 }
5312
5313 Boolean pushed = FALSE;
5314 char *lastParseAttempt;
5315
5316 void
5317 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5318 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5319   int fromX, fromY, toX, toY; char promoChar;
5320   ChessMove moveType;
5321   Boolean valid;
5322   int nr = 0;
5323
5324   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5325     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5326     pushed = TRUE;
5327   }
5328   endPV = forwardMostMove;
5329   do {
5330     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5331     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5332     lastParseAttempt = pv;
5333     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5334 if(appData.debugMode){
5335 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
5336 }
5337     if(!valid && nr == 0 &&
5338        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5339         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5340         // Hande case where played move is different from leading PV move
5341         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5342         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5343         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5344         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5345           endPV += 2; // if position different, keep this
5346           moveList[endPV-1][0] = fromX + AAA;
5347           moveList[endPV-1][1] = fromY + ONE;
5348           moveList[endPV-1][2] = toX + AAA;
5349           moveList[endPV-1][3] = toY + ONE;
5350           parseList[endPV-1][0] = NULLCHAR;
5351           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5352         }
5353       }
5354     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5355     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5356     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5357     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5358         valid++; // allow comments in PV
5359         continue;
5360     }
5361     nr++;
5362     if(endPV+1 > framePtr) break; // no space, truncate
5363     if(!valid) break;
5364     endPV++;
5365     CopyBoard(boards[endPV], boards[endPV-1]);
5366     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5367     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5368     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5369     CoordsToAlgebraic(boards[endPV - 1],
5370                              PosFlags(endPV - 1),
5371                              fromY, fromX, toY, toX, promoChar,
5372                              parseList[endPV - 1]);
5373   } while(valid);
5374   if(atEnd == 2) return; // used hidden, for PV conversion
5375   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5376   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5377   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5378                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5379   DrawPosition(TRUE, boards[currentMove]);
5380 }
5381
5382 int
5383 MultiPV(ChessProgramState *cps)
5384 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5385         int i;
5386         for(i=0; i<cps->nrOptions; i++)
5387             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5388                 return i;
5389         return -1;
5390 }
5391
5392 Boolean
5393 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5394 {
5395         int startPV, multi, lineStart, origIndex = index;
5396         char *p, buf2[MSG_SIZ];
5397
5398         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5399         lastX = x; lastY = y;
5400         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5401         lineStart = startPV = index;
5402         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5403         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5404         index = startPV;
5405         do{ while(buf[index] && buf[index] != '\n') index++;
5406         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5407         buf[index] = 0;
5408         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5409                 int n = first.option[multi].value;
5410                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5411                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5412                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5413                 first.option[multi].value = n;
5414                 *start = *end = 0;
5415                 return FALSE;
5416         }
5417         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5418         *start = startPV; *end = index-1;
5419         return TRUE;
5420 }
5421
5422 char *
5423 PvToSAN(char *pv)
5424 {
5425         static char buf[10*MSG_SIZ];
5426         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5427         *buf = NULLCHAR;
5428         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5429         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5430         for(i = forwardMostMove; i<endPV; i++){
5431             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5432             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5433             k += strlen(buf+k);
5434         }
5435         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5436         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5437         endPV = savedEnd;
5438         return buf;
5439 }
5440
5441 Boolean
5442 LoadPV(int x, int y)
5443 { // called on right mouse click to load PV
5444   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5445   lastX = x; lastY = y;
5446   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5447   return TRUE;
5448 }
5449
5450 void
5451 UnLoadPV()
5452 {
5453   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5454   if(endPV < 0) return;
5455   endPV = -1;
5456   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5457         Boolean saveAnimate = appData.animate;
5458         if(pushed) {
5459             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5460                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5461             } else storedGames--; // abandon shelved tail of original game
5462         }
5463         pushed = FALSE;
5464         forwardMostMove = currentMove;
5465         currentMove = oldFMM;
5466         appData.animate = FALSE;
5467         ToNrEvent(forwardMostMove);
5468         appData.animate = saveAnimate;
5469   }
5470   currentMove = forwardMostMove;
5471   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5472   ClearPremoveHighlights();
5473   DrawPosition(TRUE, boards[currentMove]);
5474 }
5475
5476 void
5477 MovePV(int x, int y, int h)
5478 { // step through PV based on mouse coordinates (called on mouse move)
5479   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5480
5481   // we must somehow check if right button is still down (might be released off board!)
5482   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5483   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5484   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5485   if(!step) return;
5486   lastX = x; lastY = y;
5487
5488   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5489   if(endPV < 0) return;
5490   if(y < margin) step = 1; else
5491   if(y > h - margin) step = -1;
5492   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5493   currentMove += step;
5494   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5495   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5496                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5497   DrawPosition(FALSE, boards[currentMove]);
5498 }
5499
5500
5501 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5502 // All positions will have equal probability, but the current method will not provide a unique
5503 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5504 #define DARK 1
5505 #define LITE 2
5506 #define ANY 3
5507
5508 int squaresLeft[4];
5509 int piecesLeft[(int)BlackPawn];
5510 int seed, nrOfShuffles;
5511
5512 void GetPositionNumber()
5513 {       // sets global variable seed
5514         int i;
5515
5516         seed = appData.defaultFrcPosition;
5517         if(seed < 0) { // randomize based on time for negative FRC position numbers
5518                 for(i=0; i<50; i++) seed += random();
5519                 seed = random() ^ random() >> 8 ^ random() << 8;
5520                 if(seed<0) seed = -seed;
5521         }
5522 }
5523
5524 int put(Board board, int pieceType, int rank, int n, int shade)
5525 // put the piece on the (n-1)-th empty squares of the given shade
5526 {
5527         int i;
5528
5529         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5530                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5531                         board[rank][i] = (ChessSquare) pieceType;
5532                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5533                         squaresLeft[ANY]--;
5534                         piecesLeft[pieceType]--;
5535                         return i;
5536                 }
5537         }
5538         return -1;
5539 }
5540
5541
5542 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5543 // calculate where the next piece goes, (any empty square), and put it there
5544 {
5545         int i;
5546
5547         i = seed % squaresLeft[shade];
5548         nrOfShuffles *= squaresLeft[shade];
5549         seed /= squaresLeft[shade];
5550         put(board, pieceType, rank, i, shade);
5551 }
5552
5553 void AddTwoPieces(Board board, int pieceType, int rank)
5554 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5555 {
5556         int i, n=squaresLeft[ANY], j=n-1, k;
5557
5558         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5559         i = seed % k;  // pick one
5560         nrOfShuffles *= k;
5561         seed /= k;
5562         while(i >= j) i -= j--;
5563         j = n - 1 - j; i += j;
5564         put(board, pieceType, rank, j, ANY);
5565         put(board, pieceType, rank, i, ANY);
5566 }
5567
5568 void SetUpShuffle(Board board, int number)
5569 {
5570         int i, p, first=1;
5571
5572         GetPositionNumber(); nrOfShuffles = 1;
5573
5574         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5575         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5576         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5577
5578         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5579
5580         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5581             p = (int) board[0][i];
5582             if(p < (int) BlackPawn) piecesLeft[p] ++;
5583             board[0][i] = EmptySquare;
5584         }
5585
5586         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5587             // shuffles restricted to allow normal castling put KRR first
5588             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5589                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5590             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5591                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5592             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5593                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5594             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5595                 put(board, WhiteRook, 0, 0, ANY);
5596             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5597         }
5598
5599         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5600             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5601             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5602                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5603                 while(piecesLeft[p] >= 2) {
5604                     AddOnePiece(board, p, 0, LITE);
5605                     AddOnePiece(board, p, 0, DARK);
5606                 }
5607                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5608             }
5609
5610         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5611             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5612             // but we leave King and Rooks for last, to possibly obey FRC restriction
5613             if(p == (int)WhiteRook) continue;
5614             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5615             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5616         }
5617
5618         // now everything is placed, except perhaps King (Unicorn) and Rooks
5619
5620         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5621             // Last King gets castling rights
5622             while(piecesLeft[(int)WhiteUnicorn]) {
5623                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5624                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5625             }
5626
5627             while(piecesLeft[(int)WhiteKing]) {
5628                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5629                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5630             }
5631
5632
5633         } else {
5634             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5635             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5636         }
5637
5638         // Only Rooks can be left; simply place them all
5639         while(piecesLeft[(int)WhiteRook]) {
5640                 i = put(board, WhiteRook, 0, 0, ANY);
5641                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5642                         if(first) {
5643                                 first=0;
5644                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5645                         }
5646                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5647                 }
5648         }
5649         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5650             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5651         }
5652
5653         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5654 }
5655
5656 int SetCharTable( char *table, const char * map )
5657 /* [HGM] moved here from winboard.c because of its general usefulness */
5658 /*       Basically a safe strcpy that uses the last character as King */
5659 {
5660     int result = FALSE; int NrPieces;
5661
5662     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5663                     && NrPieces >= 12 && !(NrPieces&1)) {
5664         int i; /* [HGM] Accept even length from 12 to 34 */
5665
5666         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5667         for( i=0; i<NrPieces/2-1; i++ ) {
5668             table[i] = map[i];
5669             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5670         }
5671         table[(int) WhiteKing]  = map[NrPieces/2-1];
5672         table[(int) BlackKing]  = map[NrPieces-1];
5673
5674         result = TRUE;
5675     }
5676
5677     return result;
5678 }
5679
5680 void Prelude(Board board)
5681 {       // [HGM] superchess: random selection of exo-pieces
5682         int i, j, k; ChessSquare p;
5683         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5684
5685         GetPositionNumber(); // use FRC position number
5686
5687         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5688             SetCharTable(pieceToChar, appData.pieceToCharTable);
5689             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5690                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5691         }
5692
5693         j = seed%4;                 seed /= 4;
5694         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5695         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5696         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5697         j = seed%3 + (seed%3 >= j); seed /= 3;
5698         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5699         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5700         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5701         j = seed%3;                 seed /= 3;
5702         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5703         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5704         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5705         j = seed%2 + (seed%2 >= j); seed /= 2;
5706         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5707         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5708         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5709         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5710         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5711         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5712         put(board, exoPieces[0],    0, 0, ANY);
5713         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5714 }
5715
5716 void
5717 InitPosition(redraw)
5718      int redraw;
5719 {
5720     ChessSquare (* pieces)[BOARD_FILES];
5721     int i, j, pawnRow, overrule,
5722     oldx = gameInfo.boardWidth,
5723     oldy = gameInfo.boardHeight,
5724     oldh = gameInfo.holdingsWidth;
5725     static int oldv;
5726
5727     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5728
5729     /* [AS] Initialize pv info list [HGM] and game status */
5730     {
5731         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5732             pvInfoList[i].depth = 0;
5733             boards[i][EP_STATUS] = EP_NONE;
5734             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5735         }
5736
5737         initialRulePlies = 0; /* 50-move counter start */
5738
5739         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5740         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5741     }
5742
5743
5744     /* [HGM] logic here is completely changed. In stead of full positions */
5745     /* the initialized data only consist of the two backranks. The switch */
5746     /* selects which one we will use, which is than copied to the Board   */
5747     /* initialPosition, which for the rest is initialized by Pawns and    */
5748     /* empty squares. This initial position is then copied to boards[0],  */
5749     /* possibly after shuffling, so that it remains available.            */
5750
5751     gameInfo.holdingsWidth = 0; /* default board sizes */
5752     gameInfo.boardWidth    = 8;
5753     gameInfo.boardHeight   = 8;
5754     gameInfo.holdingsSize  = 0;
5755     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5756     for(i=0; i<BOARD_FILES-2; i++)
5757       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5758     initialPosition[EP_STATUS] = EP_NONE;
5759     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5760     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5761          SetCharTable(pieceNickName, appData.pieceNickNames);
5762     else SetCharTable(pieceNickName, "............");
5763     pieces = FIDEArray;
5764
5765     switch (gameInfo.variant) {
5766     case VariantFischeRandom:
5767       shuffleOpenings = TRUE;
5768     default:
5769       break;
5770     case VariantShatranj:
5771       pieces = ShatranjArray;
5772       nrCastlingRights = 0;
5773       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5774       break;
5775     case VariantMakruk:
5776       pieces = makrukArray;
5777       nrCastlingRights = 0;
5778       startedFromSetupPosition = TRUE;
5779       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5780       break;
5781     case VariantTwoKings:
5782       pieces = twoKingsArray;
5783       break;
5784     case VariantGrand:
5785       pieces = GrandArray;
5786       nrCastlingRights = 0;
5787       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5788       gameInfo.boardWidth = 10;
5789       gameInfo.boardHeight = 10;
5790       gameInfo.holdingsSize = 7;
5791       break;
5792     case VariantCapaRandom:
5793       shuffleOpenings = TRUE;
5794     case VariantCapablanca:
5795       pieces = CapablancaArray;
5796       gameInfo.boardWidth = 10;
5797       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5798       break;
5799     case VariantGothic:
5800       pieces = GothicArray;
5801       gameInfo.boardWidth = 10;
5802       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5803       break;
5804     case VariantSChess:
5805       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5806       gameInfo.holdingsSize = 7;
5807       break;
5808     case VariantJanus:
5809       pieces = JanusArray;
5810       gameInfo.boardWidth = 10;
5811       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5812       nrCastlingRights = 6;
5813         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5814         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5815         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5816         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5817         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5818         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5819       break;
5820     case VariantFalcon:
5821       pieces = FalconArray;
5822       gameInfo.boardWidth = 10;
5823       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5824       break;
5825     case VariantXiangqi:
5826       pieces = XiangqiArray;
5827       gameInfo.boardWidth  = 9;
5828       gameInfo.boardHeight = 10;
5829       nrCastlingRights = 0;
5830       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5831       break;
5832     case VariantShogi:
5833       pieces = ShogiArray;
5834       gameInfo.boardWidth  = 9;
5835       gameInfo.boardHeight = 9;
5836       gameInfo.holdingsSize = 7;
5837       nrCastlingRights = 0;
5838       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5839       break;
5840     case VariantCourier:
5841       pieces = CourierArray;
5842       gameInfo.boardWidth  = 12;
5843       nrCastlingRights = 0;
5844       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5845       break;
5846     case VariantKnightmate:
5847       pieces = KnightmateArray;
5848       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5849       break;
5850     case VariantSpartan:
5851       pieces = SpartanArray;
5852       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5853       break;
5854     case VariantFairy:
5855       pieces = fairyArray;
5856       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5857       break;
5858     case VariantGreat:
5859       pieces = GreatArray;
5860       gameInfo.boardWidth = 10;
5861       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5862       gameInfo.holdingsSize = 8;
5863       break;
5864     case VariantSuper:
5865       pieces = FIDEArray;
5866       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5867       gameInfo.holdingsSize = 8;
5868       startedFromSetupPosition = TRUE;
5869       break;
5870     case VariantCrazyhouse:
5871     case VariantBughouse:
5872       pieces = FIDEArray;
5873       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5874       gameInfo.holdingsSize = 5;
5875       break;
5876     case VariantWildCastle:
5877       pieces = FIDEArray;
5878       /* !!?shuffle with kings guaranteed to be on d or e file */
5879       shuffleOpenings = 1;
5880       break;
5881     case VariantNoCastle:
5882       pieces = FIDEArray;
5883       nrCastlingRights = 0;
5884       /* !!?unconstrained back-rank shuffle */
5885       shuffleOpenings = 1;
5886       break;
5887     }
5888
5889     overrule = 0;
5890     if(appData.NrFiles >= 0) {
5891         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5892         gameInfo.boardWidth = appData.NrFiles;
5893     }
5894     if(appData.NrRanks >= 0) {
5895         gameInfo.boardHeight = appData.NrRanks;
5896     }
5897     if(appData.holdingsSize >= 0) {
5898         i = appData.holdingsSize;
5899         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5900         gameInfo.holdingsSize = i;
5901     }
5902     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5903     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5904         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5905
5906     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5907     if(pawnRow < 1) pawnRow = 1;
5908     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5909
5910     /* User pieceToChar list overrules defaults */
5911     if(appData.pieceToCharTable != NULL)
5912         SetCharTable(pieceToChar, appData.pieceToCharTable);
5913
5914     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5915
5916         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5917             s = (ChessSquare) 0; /* account holding counts in guard band */
5918         for( i=0; i<BOARD_HEIGHT; i++ )
5919             initialPosition[i][j] = s;
5920
5921         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5922         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5923         initialPosition[pawnRow][j] = WhitePawn;
5924         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5925         if(gameInfo.variant == VariantXiangqi) {
5926             if(j&1) {
5927                 initialPosition[pawnRow][j] =
5928                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5929                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5930                    initialPosition[2][j] = WhiteCannon;
5931                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5932                 }
5933             }
5934         }
5935         if(gameInfo.variant == VariantGrand) {
5936             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5937                initialPosition[0][j] = WhiteRook;
5938                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5939             }
5940         }
5941         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5942     }
5943     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5944
5945             j=BOARD_LEFT+1;
5946             initialPosition[1][j] = WhiteBishop;
5947             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5948             j=BOARD_RGHT-2;
5949             initialPosition[1][j] = WhiteRook;
5950             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5951     }
5952
5953     if( nrCastlingRights == -1) {
5954         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5955         /*       This sets default castling rights from none to normal corners   */
5956         /* Variants with other castling rights must set them themselves above    */
5957         nrCastlingRights = 6;
5958
5959         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5960         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5961         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5962         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5963         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5964         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5965      }
5966
5967      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5968      if(gameInfo.variant == VariantGreat) { // promotion commoners
5969         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5970         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5971         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5972         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5973      }
5974      if( gameInfo.variant == VariantSChess ) {
5975       initialPosition[1][0] = BlackMarshall;
5976       initialPosition[2][0] = BlackAngel;
5977       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5978       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5979       initialPosition[1][1] = initialPosition[2][1] = 
5980       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5981      }
5982   if (appData.debugMode) {
5983     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5984   }
5985     if(shuffleOpenings) {
5986         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5987         startedFromSetupPosition = TRUE;
5988     }
5989     if(startedFromPositionFile) {
5990       /* [HGM] loadPos: use PositionFile for every new game */
5991       CopyBoard(initialPosition, filePosition);
5992       for(i=0; i<nrCastlingRights; i++)
5993           initialRights[i] = filePosition[CASTLING][i];
5994       startedFromSetupPosition = TRUE;
5995     }
5996
5997     CopyBoard(boards[0], initialPosition);
5998
5999     if(oldx != gameInfo.boardWidth ||
6000        oldy != gameInfo.boardHeight ||
6001        oldv != gameInfo.variant ||
6002        oldh != gameInfo.holdingsWidth
6003                                          )
6004             InitDrawingSizes(-2 ,0);
6005
6006     oldv = gameInfo.variant;
6007     if (redraw)
6008       DrawPosition(TRUE, boards[currentMove]);
6009 }
6010
6011 void
6012 SendBoard(cps, moveNum)
6013      ChessProgramState *cps;
6014      int moveNum;
6015 {
6016     char message[MSG_SIZ];
6017
6018     if (cps->useSetboard) {
6019       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6020       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6021       SendToProgram(message, cps);
6022       free(fen);
6023
6024     } else {
6025       ChessSquare *bp;
6026       int i, j, left=0, right=BOARD_WIDTH;
6027       /* Kludge to set black to move, avoiding the troublesome and now
6028        * deprecated "black" command.
6029        */
6030       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6031         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6032
6033       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6034
6035       SendToProgram("edit\n", cps);
6036       SendToProgram("#\n", cps);
6037       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6038         bp = &boards[moveNum][i][left];
6039         for (j = left; j < right; j++, bp++) {
6040           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6041           if ((int) *bp < (int) BlackPawn) {
6042             if(j == BOARD_RGHT+1)
6043                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6044             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6045             if(message[0] == '+' || message[0] == '~') {
6046               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6047                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6048                         AAA + j, ONE + i);
6049             }
6050             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6051                 message[1] = BOARD_RGHT   - 1 - j + '1';
6052                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6053             }
6054             SendToProgram(message, cps);
6055           }
6056         }
6057       }
6058
6059       SendToProgram("c\n", cps);
6060       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6061         bp = &boards[moveNum][i][left];
6062         for (j = left; j < right; j++, bp++) {
6063           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6064           if (((int) *bp != (int) EmptySquare)
6065               && ((int) *bp >= (int) BlackPawn)) {
6066             if(j == BOARD_LEFT-2)
6067                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6068             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6069                     AAA + j, ONE + i);
6070             if(message[0] == '+' || message[0] == '~') {
6071               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6072                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6073                         AAA + j, ONE + i);
6074             }
6075             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6076                 message[1] = BOARD_RGHT   - 1 - j + '1';
6077                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6078             }
6079             SendToProgram(message, cps);
6080           }
6081         }
6082       }
6083
6084       SendToProgram(".\n", cps);
6085     }
6086     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6087 }
6088
6089 ChessSquare
6090 DefaultPromoChoice(int white)
6091 {
6092     ChessSquare result;
6093     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6094         result = WhiteFerz; // no choice
6095     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6096         result= WhiteKing; // in Suicide Q is the last thing we want
6097     else if(gameInfo.variant == VariantSpartan)
6098         result = white ? WhiteQueen : WhiteAngel;
6099     else result = WhiteQueen;
6100     if(!white) result = WHITE_TO_BLACK result;
6101     return result;
6102 }
6103
6104 static int autoQueen; // [HGM] oneclick
6105
6106 int
6107 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6108 {
6109     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6110     /* [HGM] add Shogi promotions */
6111     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6112     ChessSquare piece;
6113     ChessMove moveType;
6114     Boolean premove;
6115
6116     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6117     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6118
6119     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6120       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6121         return FALSE;
6122
6123     piece = boards[currentMove][fromY][fromX];
6124     if(gameInfo.variant == VariantShogi) {
6125         promotionZoneSize = BOARD_HEIGHT/3;
6126         highestPromotingPiece = (int)WhiteFerz;
6127     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6128         promotionZoneSize = 3;
6129     }
6130
6131     // Treat Lance as Pawn when it is not representing Amazon
6132     if(gameInfo.variant != VariantSuper) {
6133         if(piece == WhiteLance) piece = WhitePawn; else
6134         if(piece == BlackLance) piece = BlackPawn;
6135     }
6136
6137     // next weed out all moves that do not touch the promotion zone at all
6138     if((int)piece >= BlackPawn) {
6139         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6140              return FALSE;
6141         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6142     } else {
6143         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6144            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6145     }
6146
6147     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6148
6149     // weed out mandatory Shogi promotions
6150     if(gameInfo.variant == VariantShogi) {
6151         if(piece >= BlackPawn) {
6152             if(toY == 0 && piece == BlackPawn ||
6153                toY == 0 && piece == BlackQueen ||
6154                toY <= 1 && piece == BlackKnight) {
6155                 *promoChoice = '+';
6156                 return FALSE;
6157             }
6158         } else {
6159             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6160                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6161                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6162                 *promoChoice = '+';
6163                 return FALSE;
6164             }
6165         }
6166     }
6167
6168     // weed out obviously illegal Pawn moves
6169     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6170         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6171         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6172         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6173         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6174         // note we are not allowed to test for valid (non-)capture, due to premove
6175     }
6176
6177     // we either have a choice what to promote to, or (in Shogi) whether to promote
6178     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6179         *promoChoice = PieceToChar(BlackFerz);  // no choice
6180         return FALSE;
6181     }
6182     // no sense asking what we must promote to if it is going to explode...
6183     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6184         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6185         return FALSE;
6186     }
6187     // give caller the default choice even if we will not make it
6188     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6189     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6190     if(        sweepSelect && gameInfo.variant != VariantGreat
6191                            && gameInfo.variant != VariantGrand
6192                            && gameInfo.variant != VariantSuper) return FALSE;
6193     if(autoQueen) return FALSE; // predetermined
6194
6195     // suppress promotion popup on illegal moves that are not premoves
6196     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6197               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6198     if(appData.testLegality && !premove) {
6199         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6200                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6201         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6202             return FALSE;
6203     }
6204
6205     return TRUE;
6206 }
6207
6208 int
6209 InPalace(row, column)
6210      int row, column;
6211 {   /* [HGM] for Xiangqi */
6212     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6213          column < (BOARD_WIDTH + 4)/2 &&
6214          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6215     return FALSE;
6216 }
6217
6218 int
6219 PieceForSquare (x, y)
6220      int x;
6221      int y;
6222 {
6223   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6224      return -1;
6225   else
6226      return boards[currentMove][y][x];
6227 }
6228
6229 int
6230 OKToStartUserMove(x, y)
6231      int x, y;
6232 {
6233     ChessSquare from_piece;
6234     int white_piece;
6235
6236     if (matchMode) return FALSE;
6237     if (gameMode == EditPosition) return TRUE;
6238
6239     if (x >= 0 && y >= 0)
6240       from_piece = boards[currentMove][y][x];
6241     else
6242       from_piece = EmptySquare;
6243
6244     if (from_piece == EmptySquare) return FALSE;
6245
6246     white_piece = (int)from_piece >= (int)WhitePawn &&
6247       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6248
6249     switch (gameMode) {
6250       case AnalyzeFile:
6251       case TwoMachinesPlay:
6252       case EndOfGame:
6253         return FALSE;
6254
6255       case IcsObserving:
6256       case IcsIdle:
6257         return FALSE;
6258
6259       case MachinePlaysWhite:
6260       case IcsPlayingBlack:
6261         if (appData.zippyPlay) return FALSE;
6262         if (white_piece) {
6263             DisplayMoveError(_("You are playing Black"));
6264             return FALSE;
6265         }
6266         break;
6267
6268       case MachinePlaysBlack:
6269       case IcsPlayingWhite:
6270         if (appData.zippyPlay) return FALSE;
6271         if (!white_piece) {
6272             DisplayMoveError(_("You are playing White"));
6273             return FALSE;
6274         }
6275         break;
6276
6277       case PlayFromGameFile:
6278             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6279       case EditGame:
6280         if (!white_piece && WhiteOnMove(currentMove)) {
6281             DisplayMoveError(_("It is White's turn"));
6282             return FALSE;
6283         }
6284         if (white_piece && !WhiteOnMove(currentMove)) {
6285             DisplayMoveError(_("It is Black's turn"));
6286             return FALSE;
6287         }
6288         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6289             /* Editing correspondence game history */
6290             /* Could disallow this or prompt for confirmation */
6291             cmailOldMove = -1;
6292         }
6293         break;
6294
6295       case BeginningOfGame:
6296         if (appData.icsActive) return FALSE;
6297         if (!appData.noChessProgram) {
6298             if (!white_piece) {
6299                 DisplayMoveError(_("You are playing White"));
6300                 return FALSE;
6301             }
6302         }
6303         break;
6304
6305       case Training:
6306         if (!white_piece && WhiteOnMove(currentMove)) {
6307             DisplayMoveError(_("It is White's turn"));
6308             return FALSE;
6309         }
6310         if (white_piece && !WhiteOnMove(currentMove)) {
6311             DisplayMoveError(_("It is Black's turn"));
6312             return FALSE;
6313         }
6314         break;
6315
6316       default:
6317       case IcsExamining:
6318         break;
6319     }
6320     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6321         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6322         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6323         && gameMode != AnalyzeFile && gameMode != Training) {
6324         DisplayMoveError(_("Displayed position is not current"));
6325         return FALSE;
6326     }
6327     return TRUE;
6328 }
6329
6330 Boolean
6331 OnlyMove(int *x, int *y, Boolean captures) {
6332     DisambiguateClosure cl;
6333     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6334     switch(gameMode) {
6335       case MachinePlaysBlack:
6336       case IcsPlayingWhite:
6337       case BeginningOfGame:
6338         if(!WhiteOnMove(currentMove)) return FALSE;
6339         break;
6340       case MachinePlaysWhite:
6341       case IcsPlayingBlack:
6342         if(WhiteOnMove(currentMove)) return FALSE;
6343         break;
6344       case EditGame:
6345         break;
6346       default:
6347         return FALSE;
6348     }
6349     cl.pieceIn = EmptySquare;
6350     cl.rfIn = *y;
6351     cl.ffIn = *x;
6352     cl.rtIn = -1;
6353     cl.ftIn = -1;
6354     cl.promoCharIn = NULLCHAR;
6355     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6356     if( cl.kind == NormalMove ||
6357         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6358         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6359         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6360       fromX = cl.ff;
6361       fromY = cl.rf;
6362       *x = cl.ft;
6363       *y = cl.rt;
6364       return TRUE;
6365     }
6366     if(cl.kind != ImpossibleMove) return FALSE;
6367     cl.pieceIn = EmptySquare;
6368     cl.rfIn = -1;
6369     cl.ffIn = -1;
6370     cl.rtIn = *y;
6371     cl.ftIn = *x;
6372     cl.promoCharIn = NULLCHAR;
6373     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6374     if( cl.kind == NormalMove ||
6375         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6376         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6377         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6378       fromX = cl.ff;
6379       fromY = cl.rf;
6380       *x = cl.ft;
6381       *y = cl.rt;
6382       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6383       return TRUE;
6384     }
6385     return FALSE;
6386 }
6387
6388 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6389 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6390 int lastLoadGameUseList = FALSE;
6391 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6392 ChessMove lastLoadGameStart = EndOfFile;
6393
6394 void
6395 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6396      int fromX, fromY, toX, toY;
6397      int promoChar;
6398 {
6399     ChessMove moveType;
6400     ChessSquare pdown, pup;
6401
6402     /* Check if the user is playing in turn.  This is complicated because we
6403        let the user "pick up" a piece before it is his turn.  So the piece he
6404        tried to pick up may have been captured by the time he puts it down!
6405        Therefore we use the color the user is supposed to be playing in this
6406        test, not the color of the piece that is currently on the starting
6407        square---except in EditGame mode, where the user is playing both
6408        sides; fortunately there the capture race can't happen.  (It can
6409        now happen in IcsExamining mode, but that's just too bad.  The user
6410        will get a somewhat confusing message in that case.)
6411        */
6412
6413     switch (gameMode) {
6414       case AnalyzeFile:
6415       case TwoMachinesPlay:
6416       case EndOfGame:
6417       case IcsObserving:
6418       case IcsIdle:
6419         /* We switched into a game mode where moves are not accepted,
6420            perhaps while the mouse button was down. */
6421         return;
6422
6423       case MachinePlaysWhite:
6424         /* User is moving for Black */
6425         if (WhiteOnMove(currentMove)) {
6426             DisplayMoveError(_("It is White's turn"));
6427             return;
6428         }
6429         break;
6430
6431       case MachinePlaysBlack:
6432         /* User is moving for White */
6433         if (!WhiteOnMove(currentMove)) {
6434             DisplayMoveError(_("It is Black's turn"));
6435             return;
6436         }
6437         break;
6438
6439       case PlayFromGameFile:
6440             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6441       case EditGame:
6442       case IcsExamining:
6443       case BeginningOfGame:
6444       case AnalyzeMode:
6445       case Training:
6446         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6447         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6448             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6449             /* User is moving for Black */
6450             if (WhiteOnMove(currentMove)) {
6451                 DisplayMoveError(_("It is White's turn"));
6452                 return;
6453             }
6454         } else {
6455             /* User is moving for White */
6456             if (!WhiteOnMove(currentMove)) {
6457                 DisplayMoveError(_("It is Black's turn"));
6458                 return;
6459             }
6460         }
6461         break;
6462
6463       case IcsPlayingBlack:
6464         /* User is moving for Black */
6465         if (WhiteOnMove(currentMove)) {
6466             if (!appData.premove) {
6467                 DisplayMoveError(_("It is White's turn"));
6468             } else if (toX >= 0 && toY >= 0) {
6469                 premoveToX = toX;
6470                 premoveToY = toY;
6471                 premoveFromX = fromX;
6472                 premoveFromY = fromY;
6473                 premovePromoChar = promoChar;
6474                 gotPremove = 1;
6475                 if (appData.debugMode)
6476                     fprintf(debugFP, "Got premove: fromX %d,"
6477                             "fromY %d, toX %d, toY %d\n",
6478                             fromX, fromY, toX, toY);
6479             }
6480             return;
6481         }
6482         break;
6483
6484       case IcsPlayingWhite:
6485         /* User is moving for White */
6486         if (!WhiteOnMove(currentMove)) {
6487             if (!appData.premove) {
6488                 DisplayMoveError(_("It is Black's turn"));
6489             } else if (toX >= 0 && toY >= 0) {
6490                 premoveToX = toX;
6491                 premoveToY = toY;
6492                 premoveFromX = fromX;
6493                 premoveFromY = fromY;
6494                 premovePromoChar = promoChar;
6495                 gotPremove = 1;
6496                 if (appData.debugMode)
6497                     fprintf(debugFP, "Got premove: fromX %d,"
6498                             "fromY %d, toX %d, toY %d\n",
6499                             fromX, fromY, toX, toY);
6500             }
6501             return;
6502         }
6503         break;
6504
6505       default:
6506         break;
6507
6508       case EditPosition:
6509         /* EditPosition, empty square, or different color piece;
6510            click-click move is possible */
6511         if (toX == -2 || toY == -2) {
6512             boards[0][fromY][fromX] = EmptySquare;
6513             DrawPosition(FALSE, boards[currentMove]);
6514             return;
6515         } else if (toX >= 0 && toY >= 0) {
6516             boards[0][toY][toX] = boards[0][fromY][fromX];
6517             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6518                 if(boards[0][fromY][0] != EmptySquare) {
6519                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6520                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6521                 }
6522             } else
6523             if(fromX == BOARD_RGHT+1) {
6524                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6525                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6526                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6527                 }
6528             } else
6529             boards[0][fromY][fromX] = EmptySquare;
6530             DrawPosition(FALSE, boards[currentMove]);
6531             return;
6532         }
6533         return;
6534     }
6535
6536     if(toX < 0 || toY < 0) return;
6537     pdown = boards[currentMove][fromY][fromX];
6538     pup = boards[currentMove][toY][toX];
6539
6540     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6541     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6542          if( pup != EmptySquare ) return;
6543          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6544            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6545                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6546            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6547            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6548            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6549            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6550          fromY = DROP_RANK;
6551     }
6552
6553     /* [HGM] always test for legality, to get promotion info */
6554     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6555                                          fromY, fromX, toY, toX, promoChar);
6556
6557     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6558
6559     /* [HGM] but possibly ignore an IllegalMove result */
6560     if (appData.testLegality) {
6561         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6562             DisplayMoveError(_("Illegal move"));
6563             return;
6564         }
6565     }
6566
6567     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6568 }
6569
6570 /* Common tail of UserMoveEvent and DropMenuEvent */
6571 int
6572 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6573      ChessMove moveType;
6574      int fromX, fromY, toX, toY;
6575      /*char*/int promoChar;
6576 {
6577     char *bookHit = 0;
6578
6579     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6580         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6581         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6582         if(WhiteOnMove(currentMove)) {
6583             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6584         } else {
6585             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6586         }
6587     }
6588
6589     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6590        move type in caller when we know the move is a legal promotion */
6591     if(moveType == NormalMove && promoChar)
6592         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6593
6594     /* [HGM] <popupFix> The following if has been moved here from
6595        UserMoveEvent(). Because it seemed to belong here (why not allow
6596        piece drops in training games?), and because it can only be
6597        performed after it is known to what we promote. */
6598     if (gameMode == Training) {
6599       /* compare the move played on the board to the next move in the
6600        * game. If they match, display the move and the opponent's response.
6601        * If they don't match, display an error message.
6602        */
6603       int saveAnimate;
6604       Board testBoard;
6605       CopyBoard(testBoard, boards[currentMove]);
6606       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6607
6608       if (CompareBoards(testBoard, boards[currentMove+1])) {
6609         ForwardInner(currentMove+1);
6610
6611         /* Autoplay the opponent's response.
6612          * if appData.animate was TRUE when Training mode was entered,
6613          * the response will be animated.
6614          */
6615         saveAnimate = appData.animate;
6616         appData.animate = animateTraining;
6617         ForwardInner(currentMove+1);
6618         appData.animate = saveAnimate;
6619
6620         /* check for the end of the game */
6621         if (currentMove >= forwardMostMove) {
6622           gameMode = PlayFromGameFile;
6623           ModeHighlight();
6624           SetTrainingModeOff();
6625           DisplayInformation(_("End of game"));
6626         }
6627       } else {
6628         DisplayError(_("Incorrect move"), 0);
6629       }
6630       return 1;
6631     }
6632
6633   /* Ok, now we know that the move is good, so we can kill
6634      the previous line in Analysis Mode */
6635   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6636                                 && currentMove < forwardMostMove) {
6637     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6638     else forwardMostMove = currentMove;
6639   }
6640
6641   /* If we need the chess program but it's dead, restart it */
6642   ResurrectChessProgram();
6643
6644   /* A user move restarts a paused game*/
6645   if (pausing)
6646     PauseEvent();
6647
6648   thinkOutput[0] = NULLCHAR;
6649
6650   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6651
6652   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6653     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6654     return 1;
6655   }
6656
6657   if (gameMode == BeginningOfGame) {
6658     if (appData.noChessProgram) {
6659       gameMode = EditGame;
6660       SetGameInfo();
6661     } else {
6662       char buf[MSG_SIZ];
6663       gameMode = MachinePlaysBlack;
6664       StartClocks();
6665       SetGameInfo();
6666       snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
6667       DisplayTitle(buf);
6668       if (first.sendName) {
6669         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6670         SendToProgram(buf, &first);
6671       }
6672       StartClocks();
6673     }
6674     ModeHighlight();
6675   }
6676
6677   /* Relay move to ICS or chess engine */
6678   if (appData.icsActive) {
6679     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6680         gameMode == IcsExamining) {
6681       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6682         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6683         SendToICS("draw ");
6684         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6685       }
6686       // also send plain move, in case ICS does not understand atomic claims
6687       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6688       ics_user_moved = 1;
6689     }
6690   } else {
6691     if (first.sendTime && (gameMode == BeginningOfGame ||
6692                            gameMode == MachinePlaysWhite ||
6693                            gameMode == MachinePlaysBlack)) {
6694       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6695     }
6696     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6697          // [HGM] book: if program might be playing, let it use book
6698         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6699         first.maybeThinking = TRUE;
6700     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6701         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6702         SendBoard(&first, currentMove+1);
6703     } else SendMoveToProgram(forwardMostMove-1, &first);
6704     if (currentMove == cmailOldMove + 1) {
6705       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6706     }
6707   }
6708
6709   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6710
6711   switch (gameMode) {
6712   case EditGame:
6713     if(appData.testLegality)
6714     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6715     case MT_NONE:
6716     case MT_CHECK:
6717       break;
6718     case MT_CHECKMATE:
6719     case MT_STAINMATE:
6720       if (WhiteOnMove(currentMove)) {
6721         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6722       } else {
6723         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6724       }
6725       break;
6726     case MT_STALEMATE:
6727       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6728       break;
6729     }
6730     break;
6731
6732   case MachinePlaysBlack:
6733   case MachinePlaysWhite:
6734     /* disable certain menu options while machine is thinking */
6735     SetMachineThinkingEnables();
6736     break;
6737
6738   default:
6739     break;
6740   }
6741
6742   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6743   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6744
6745   if(bookHit) { // [HGM] book: simulate book reply
6746         static char bookMove[MSG_SIZ]; // a bit generous?
6747
6748         programStats.nodes = programStats.depth = programStats.time =
6749         programStats.score = programStats.got_only_move = 0;
6750         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6751
6752         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6753         strcat(bookMove, bookHit);
6754         HandleMachineMove(bookMove, &first);
6755   }
6756   return 1;
6757 }
6758
6759 void
6760 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6761      Board board;
6762      int flags;
6763      ChessMove kind;
6764      int rf, ff, rt, ft;
6765      VOIDSTAR closure;
6766 {
6767     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6768     Markers *m = (Markers *) closure;
6769     if(rf == fromY && ff == fromX)
6770         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6771                          || kind == WhiteCapturesEnPassant
6772                          || kind == BlackCapturesEnPassant);
6773     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6774 }
6775
6776 void
6777 MarkTargetSquares(int clear)
6778 {
6779   int x, y;
6780   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6781      !appData.testLegality || gameMode == EditPosition) return;
6782   if(clear) {
6783     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6784   } else {
6785     int capt = 0;
6786     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6787     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6788       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6789       if(capt)
6790       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6791     }
6792   }
6793   DrawPosition(TRUE, NULL);
6794 }
6795
6796 int
6797 Explode(Board board, int fromX, int fromY, int toX, int toY)
6798 {
6799     if(gameInfo.variant == VariantAtomic &&
6800        (board[toY][toX] != EmptySquare ||                     // capture?
6801         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6802                          board[fromY][fromX] == BlackPawn   )
6803       )) {
6804         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6805         return TRUE;
6806     }
6807     return FALSE;
6808 }
6809
6810 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6811
6812 int CanPromote(ChessSquare piece, int y)
6813 {
6814         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6815         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6816         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6817            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6818            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6819                                                   gameInfo.variant == VariantMakruk) return FALSE;
6820         return (piece == BlackPawn && y == 1 ||
6821                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6822                 piece == BlackLance && y == 1 ||
6823                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6824 }
6825
6826 void LeftClick(ClickType clickType, int xPix, int yPix)
6827 {
6828     int x, y;
6829     Boolean saveAnimate;
6830     static int second = 0, promotionChoice = 0, clearFlag = 0;
6831     char promoChoice = NULLCHAR;
6832     ChessSquare piece;
6833
6834     if(appData.seekGraph && appData.icsActive && loggedOn &&
6835         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6836         SeekGraphClick(clickType, xPix, yPix, 0);
6837         return;
6838     }
6839
6840     if (clickType == Press) ErrorPopDown();
6841
6842     x = EventToSquare(xPix, BOARD_WIDTH);
6843     y = EventToSquare(yPix, BOARD_HEIGHT);
6844     if (!flipView && y >= 0) {
6845         y = BOARD_HEIGHT - 1 - y;
6846     }
6847     if (flipView && x >= 0) {
6848         x = BOARD_WIDTH - 1 - x;
6849     }
6850
6851     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6852         defaultPromoChoice = promoSweep;
6853         promoSweep = EmptySquare;   // terminate sweep
6854         promoDefaultAltered = TRUE;
6855         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6856     }
6857
6858     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6859         if(clickType == Release) return; // ignore upclick of click-click destination
6860         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6861         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6862         if(gameInfo.holdingsWidth &&
6863                 (WhiteOnMove(currentMove)
6864                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6865                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6866             // click in right holdings, for determining promotion piece
6867             ChessSquare p = boards[currentMove][y][x];
6868             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6869             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6870             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6871                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6872                 fromX = fromY = -1;
6873                 return;
6874             }
6875         }
6876         DrawPosition(FALSE, boards[currentMove]);
6877         return;
6878     }
6879
6880     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6881     if(clickType == Press
6882             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6883               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6884               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6885         return;
6886
6887     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6888         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6889
6890     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6891         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6892                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6893         defaultPromoChoice = DefaultPromoChoice(side);
6894     }
6895
6896     autoQueen = appData.alwaysPromoteToQueen;
6897
6898     if (fromX == -1) {
6899       int originalY = y;
6900       gatingPiece = EmptySquare;
6901       if (clickType != Press) {
6902         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6903             DragPieceEnd(xPix, yPix); dragging = 0;
6904             DrawPosition(FALSE, NULL);
6905         }
6906         return;
6907       }
6908       fromX = x; fromY = y; toX = toY = -1;
6909       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6910          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6911          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6912             /* First square */
6913             if (OKToStartUserMove(fromX, fromY)) {
6914                 second = 0;
6915                 MarkTargetSquares(0);
6916                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6917                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6918                     promoSweep = defaultPromoChoice;
6919                     selectFlag = 0; lastX = xPix; lastY = yPix;
6920                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6921                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6922                 }
6923                 if (appData.highlightDragging) {
6924                     SetHighlights(fromX, fromY, -1, -1);
6925                 }
6926             } else fromX = fromY = -1;
6927             return;
6928         }
6929     }
6930
6931     /* fromX != -1 */
6932     if (clickType == Press && gameMode != EditPosition) {
6933         ChessSquare fromP;
6934         ChessSquare toP;
6935         int frc;
6936
6937         // ignore off-board to clicks
6938         if(y < 0 || x < 0) return;
6939
6940         /* Check if clicking again on the same color piece */
6941         fromP = boards[currentMove][fromY][fromX];
6942         toP = boards[currentMove][y][x];
6943         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6944         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6945              WhitePawn <= toP && toP <= WhiteKing &&
6946              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6947              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6948             (BlackPawn <= fromP && fromP <= BlackKing &&
6949              BlackPawn <= toP && toP <= BlackKing &&
6950              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6951              !(fromP == BlackKing && toP == BlackRook && frc))) {
6952             /* Clicked again on same color piece -- changed his mind */
6953             second = (x == fromX && y == fromY);
6954             promoDefaultAltered = FALSE;
6955             MarkTargetSquares(1);
6956            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6957             if (appData.highlightDragging) {
6958                 SetHighlights(x, y, -1, -1);
6959             } else {
6960                 ClearHighlights();
6961             }
6962             if (OKToStartUserMove(x, y)) {
6963                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6964                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6965                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6966                  gatingPiece = boards[currentMove][fromY][fromX];
6967                 else gatingPiece = EmptySquare;
6968                 fromX = x;
6969                 fromY = y; dragging = 1;
6970                 MarkTargetSquares(0);
6971                 DragPieceBegin(xPix, yPix, FALSE);
6972                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6973                     promoSweep = defaultPromoChoice;
6974                     selectFlag = 0; lastX = xPix; lastY = yPix;
6975                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6976                 }
6977             }
6978            }
6979            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6980            second = FALSE; 
6981         }
6982         // ignore clicks on holdings
6983         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6984     }
6985
6986     if (clickType == Release && x == fromX && y == fromY) {
6987         DragPieceEnd(xPix, yPix); dragging = 0;
6988         if(clearFlag) {
6989             // a deferred attempt to click-click move an empty square on top of a piece
6990             boards[currentMove][y][x] = EmptySquare;
6991             ClearHighlights();
6992             DrawPosition(FALSE, boards[currentMove]);
6993             fromX = fromY = -1; clearFlag = 0;
6994             return;
6995         }
6996         if (appData.animateDragging) {
6997             /* Undo animation damage if any */
6998             DrawPosition(FALSE, NULL);
6999         }
7000         if (second) {
7001             /* Second up/down in same square; just abort move */
7002             second = 0;
7003             fromX = fromY = -1;
7004             gatingPiece = EmptySquare;
7005             ClearHighlights();
7006             gotPremove = 0;
7007             ClearPremoveHighlights();
7008         } else {
7009             /* First upclick in same square; start click-click mode */
7010             SetHighlights(x, y, -1, -1);
7011         }
7012         return;
7013     }
7014
7015     clearFlag = 0;
7016
7017     /* we now have a different from- and (possibly off-board) to-square */
7018     /* Completed move */
7019     toX = x;
7020     toY = y;
7021     saveAnimate = appData.animate;
7022     MarkTargetSquares(1);
7023     if (clickType == Press) {
7024         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7025             // must be Edit Position mode with empty-square selected
7026             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7027             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7028             return;
7029         }
7030         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7031             ChessSquare piece = boards[currentMove][fromY][fromX];
7032             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7033             promoSweep = defaultPromoChoice;
7034             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7035             selectFlag = 0; lastX = xPix; lastY = yPix;
7036             Sweep(0); // Pawn that is going to promote: preview promotion piece
7037             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7038             DrawPosition(FALSE, boards[currentMove]);
7039             return;
7040         }
7041         /* Finish clickclick move */
7042         if (appData.animate || appData.highlightLastMove) {
7043             SetHighlights(fromX, fromY, toX, toY);
7044         } else {
7045             ClearHighlights();
7046         }
7047     } else {
7048         /* Finish drag move */
7049         if (appData.highlightLastMove) {
7050             SetHighlights(fromX, fromY, toX, toY);
7051         } else {
7052             ClearHighlights();
7053         }
7054         DragPieceEnd(xPix, yPix); dragging = 0;
7055         /* Don't animate move and drag both */
7056         appData.animate = FALSE;
7057     }
7058
7059     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7060     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7061         ChessSquare piece = boards[currentMove][fromY][fromX];
7062         if(gameMode == EditPosition && piece != EmptySquare &&
7063            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7064             int n;
7065
7066             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7067                 n = PieceToNumber(piece - (int)BlackPawn);
7068                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7069                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7070                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7071             } else
7072             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7073                 n = PieceToNumber(piece);
7074                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7075                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7076                 boards[currentMove][n][BOARD_WIDTH-2]++;
7077             }
7078             boards[currentMove][fromY][fromX] = EmptySquare;
7079         }
7080         ClearHighlights();
7081         fromX = fromY = -1;
7082         DrawPosition(TRUE, boards[currentMove]);
7083         return;
7084     }
7085
7086     // off-board moves should not be highlighted
7087     if(x < 0 || y < 0) ClearHighlights();
7088
7089     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7090
7091     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7092         SetHighlights(fromX, fromY, toX, toY);
7093         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7094             // [HGM] super: promotion to captured piece selected from holdings
7095             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7096             promotionChoice = TRUE;
7097             // kludge follows to temporarily execute move on display, without promoting yet
7098             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7099             boards[currentMove][toY][toX] = p;
7100             DrawPosition(FALSE, boards[currentMove]);
7101             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7102             boards[currentMove][toY][toX] = q;
7103             DisplayMessage("Click in holdings to choose piece", "");
7104             return;
7105         }
7106         PromotionPopUp();
7107     } else {
7108         int oldMove = currentMove;
7109         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7110         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7111         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7112         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7113            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7114             DrawPosition(TRUE, boards[currentMove]);
7115         fromX = fromY = -1;
7116     }
7117     appData.animate = saveAnimate;
7118     if (appData.animate || appData.animateDragging) {
7119         /* Undo animation damage if needed */
7120         DrawPosition(FALSE, NULL);
7121     }
7122 }
7123
7124 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7125 {   // front-end-free part taken out of PieceMenuPopup
7126     int whichMenu; int xSqr, ySqr;
7127
7128     if(seekGraphUp) { // [HGM] seekgraph
7129         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7130         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7131         return -2;
7132     }
7133
7134     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7135          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7136         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7137         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7138         if(action == Press)   {
7139             originalFlip = flipView;
7140             flipView = !flipView; // temporarily flip board to see game from partners perspective
7141             DrawPosition(TRUE, partnerBoard);
7142             DisplayMessage(partnerStatus, "");
7143             partnerUp = TRUE;
7144         } else if(action == Release) {
7145             flipView = originalFlip;
7146             DrawPosition(TRUE, boards[currentMove]);
7147             partnerUp = FALSE;
7148         }
7149         return -2;
7150     }
7151
7152     xSqr = EventToSquare(x, BOARD_WIDTH);
7153     ySqr = EventToSquare(y, BOARD_HEIGHT);
7154     if (action == Release) {
7155         if(pieceSweep != EmptySquare) {
7156             EditPositionMenuEvent(pieceSweep, toX, toY);
7157             pieceSweep = EmptySquare;
7158         } else UnLoadPV(); // [HGM] pv
7159     }
7160     if (action != Press) return -2; // return code to be ignored
7161     switch (gameMode) {
7162       case IcsExamining:
7163         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7164       case EditPosition:
7165         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7166         if (xSqr < 0 || ySqr < 0) return -1;
7167         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7168         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7169         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7170         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7171         NextPiece(0);
7172         return 2; // grab
7173       case IcsObserving:
7174         if(!appData.icsEngineAnalyze) return -1;
7175       case IcsPlayingWhite:
7176       case IcsPlayingBlack:
7177         if(!appData.zippyPlay) goto noZip;
7178       case AnalyzeMode:
7179       case AnalyzeFile:
7180       case MachinePlaysWhite:
7181       case MachinePlaysBlack:
7182       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7183         if (!appData.dropMenu) {
7184           LoadPV(x, y);
7185           return 2; // flag front-end to grab mouse events
7186         }
7187         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7188            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7189       case EditGame:
7190       noZip:
7191         if (xSqr < 0 || ySqr < 0) return -1;
7192         if (!appData.dropMenu || appData.testLegality &&
7193             gameInfo.variant != VariantBughouse &&
7194             gameInfo.variant != VariantCrazyhouse) return -1;
7195         whichMenu = 1; // drop menu
7196         break;
7197       default:
7198         return -1;
7199     }
7200
7201     if (((*fromX = xSqr) < 0) ||
7202         ((*fromY = ySqr) < 0)) {
7203         *fromX = *fromY = -1;
7204         return -1;
7205     }
7206     if (flipView)
7207       *fromX = BOARD_WIDTH - 1 - *fromX;
7208     else
7209       *fromY = BOARD_HEIGHT - 1 - *fromY;
7210
7211     return whichMenu;
7212 }
7213
7214 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7215 {
7216 //    char * hint = lastHint;
7217     FrontEndProgramStats stats;
7218
7219     stats.which = cps == &first ? 0 : 1;
7220     stats.depth = cpstats->depth;
7221     stats.nodes = cpstats->nodes;
7222     stats.score = cpstats->score;
7223     stats.time = cpstats->time;
7224     stats.pv = cpstats->movelist;
7225     stats.hint = lastHint;
7226     stats.an_move_index = 0;
7227     stats.an_move_count = 0;
7228
7229     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7230         stats.hint = cpstats->move_name;
7231         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7232         stats.an_move_count = cpstats->nr_moves;
7233     }
7234
7235     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
7236
7237     SetProgramStats( &stats );
7238 }
7239
7240 void
7241 ClearEngineOutputPane(int which)
7242 {
7243     static FrontEndProgramStats dummyStats;
7244     dummyStats.which = which;
7245     dummyStats.pv = "#";
7246     SetProgramStats( &dummyStats );
7247 }
7248
7249 #define MAXPLAYERS 500
7250
7251 char *
7252 TourneyStandings(int display)
7253 {
7254     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7255     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7256     char result, *p, *names[MAXPLAYERS];
7257
7258     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7259         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7260     names[0] = p = strdup(appData.participants);
7261     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7262
7263     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7264
7265     while(result = appData.results[nr]) {
7266         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7267         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7268         wScore = bScore = 0;
7269         switch(result) {
7270           case '+': wScore = 2; break;
7271           case '-': bScore = 2; break;
7272           case '=': wScore = bScore = 1; break;
7273           case ' ':
7274           case '*': return strdup("busy"); // tourney not finished
7275         }
7276         score[w] += wScore;
7277         score[b] += bScore;
7278         games[w]++;
7279         games[b]++;
7280         nr++;
7281     }
7282     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7283     for(w=0; w<nPlayers; w++) {
7284         bScore = -1;
7285         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7286         ranking[w] = b; points[w] = bScore; score[b] = -2;
7287     }
7288     p = malloc(nPlayers*34+1);
7289     for(w=0; w<nPlayers && w<display; w++)
7290         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7291     free(names[0]);
7292     return p;
7293 }
7294
7295 void
7296 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7297 {       // count all piece types
7298         int p, f, r;
7299         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7300         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7301         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7302                 p = board[r][f];
7303                 pCnt[p]++;
7304                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7305                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7306                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7307                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7308                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7309                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7310         }
7311 }
7312
7313 int
7314 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7315 {
7316         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7317         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7318
7319         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7320         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7321         if(myPawns == 2 && nMine == 3) // KPP
7322             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7323         if(myPawns == 1 && nMine == 2) // KP
7324             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7325         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7326             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7327         if(myPawns) return FALSE;
7328         if(pCnt[WhiteRook+side])
7329             return pCnt[BlackRook-side] ||
7330                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7331                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7332                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7333         if(pCnt[WhiteCannon+side]) {
7334             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7335             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7336         }
7337         if(pCnt[WhiteKnight+side])
7338             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7339         return FALSE;
7340 }
7341
7342 int
7343 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7344 {
7345         VariantClass v = gameInfo.variant;
7346
7347         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7348         if(v == VariantShatranj) return TRUE; // always winnable through baring
7349         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7350         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7351
7352         if(v == VariantXiangqi) {
7353                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7354
7355                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7356                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7357                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7358                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7359                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7360                 if(stale) // we have at least one last-rank P plus perhaps C
7361                     return majors // KPKX
7362                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7363                 else // KCA*E*
7364                     return pCnt[WhiteFerz+side] // KCAK
7365                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7366                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7367                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7368
7369         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7370                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7371
7372                 if(nMine == 1) return FALSE; // bare King
7373                 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
7374                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7375                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7376                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7377                 if(pCnt[WhiteKnight+side])
7378                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7379                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7380                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7381                 if(nBishops)
7382                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7383                 if(pCnt[WhiteAlfil+side])
7384                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7385                 if(pCnt[WhiteWazir+side])
7386                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7387         }
7388
7389         return TRUE;
7390 }
7391
7392 int
7393 CompareWithRights(Board b1, Board b2)
7394 {
7395     int rights = 0;
7396     if(!CompareBoards(b1, b2)) return FALSE;
7397     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7398     /* compare castling rights */
7399     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7400            rights++; /* King lost rights, while rook still had them */
7401     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7402         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7403            rights++; /* but at least one rook lost them */
7404     }
7405     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7406            rights++;
7407     if( b1[CASTLING][5] != NoRights ) {
7408         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7409            rights++;
7410     }
7411     return rights == 0;
7412 }
7413
7414 int
7415 Adjudicate(ChessProgramState *cps)
7416 {       // [HGM] some adjudications useful with buggy engines
7417         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7418         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7419         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7420         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7421         int k, count = 0; static int bare = 1;
7422         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7423         Boolean canAdjudicate = !appData.icsActive;
7424
7425         // most tests only when we understand the game, i.e. legality-checking on
7426             if( appData.testLegality )
7427             {   /* [HGM] Some more adjudications for obstinate engines */
7428                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7429                 static int moveCount = 6;
7430                 ChessMove result;
7431                 char *reason = NULL;
7432
7433                 /* Count what is on board. */
7434                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7435
7436                 /* Some material-based adjudications that have to be made before stalemate test */
7437                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7438                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7439                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7440                      if(canAdjudicate && appData.checkMates) {
7441                          if(engineOpponent)
7442                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7443                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7444                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7445                          return 1;
7446                      }
7447                 }
7448
7449                 /* Bare King in Shatranj (loses) or Losers (wins) */
7450                 if( nrW == 1 || nrB == 1) {
7451                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7452                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7453                      if(canAdjudicate && appData.checkMates) {
7454                          if(engineOpponent)
7455                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7456                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7457                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7458                          return 1;
7459                      }
7460                   } else
7461                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7462                   {    /* bare King */
7463                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7464                         if(canAdjudicate && appData.checkMates) {
7465                             /* but only adjudicate if adjudication enabled */
7466                             if(engineOpponent)
7467                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7468                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7469                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7470                             return 1;
7471                         }
7472                   }
7473                 } else bare = 1;
7474
7475
7476             // don't wait for engine to announce game end if we can judge ourselves
7477             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7478               case MT_CHECK:
7479                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7480                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7481                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7482                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7483                             checkCnt++;
7484                         if(checkCnt >= 2) {
7485                             reason = "Xboard adjudication: 3rd check";
7486                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7487                             break;
7488                         }
7489                     }
7490                 }
7491               case MT_NONE:
7492               default:
7493                 break;
7494               case MT_STALEMATE:
7495               case MT_STAINMATE:
7496                 reason = "Xboard adjudication: Stalemate";
7497                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7498                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7499                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7500                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7501                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7502                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7503                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7504                                                                         EP_CHECKMATE : EP_WINS);
7505                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7506                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7507                 }
7508                 break;
7509               case MT_CHECKMATE:
7510                 reason = "Xboard adjudication: Checkmate";
7511                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7512                 break;
7513             }
7514
7515                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7516                     case EP_STALEMATE:
7517                         result = GameIsDrawn; break;
7518                     case EP_CHECKMATE:
7519                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7520                     case EP_WINS:
7521                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7522                     default:
7523                         result = EndOfFile;
7524                 }
7525                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7526                     if(engineOpponent)
7527                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7528                     GameEnds( result, reason, GE_XBOARD );
7529                     return 1;
7530                 }
7531
7532                 /* Next absolutely insufficient mating material. */
7533                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7534                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7535                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7536
7537                      /* always flag draws, for judging claims */
7538                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7539
7540                      if(canAdjudicate && appData.materialDraws) {
7541                          /* but only adjudicate them if adjudication enabled */
7542                          if(engineOpponent) {
7543                            SendToProgram("force\n", engineOpponent); // suppress reply
7544                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7545                          }
7546                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7547                          return 1;
7548                      }
7549                 }
7550
7551                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7552                 if(gameInfo.variant == VariantXiangqi ?
7553                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7554                  : nrW + nrB == 4 &&
7555                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7556                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7557                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7558                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7559                    ) ) {
7560                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7561                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7562                           if(engineOpponent) {
7563                             SendToProgram("force\n", engineOpponent); // suppress reply
7564                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7565                           }
7566                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7567                           return 1;
7568                      }
7569                 } else moveCount = 6;
7570             }
7571         if (appData.debugMode) { int i;
7572             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7573                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7574                     appData.drawRepeats);
7575             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7576               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7577
7578         }
7579
7580         // Repetition draws and 50-move rule can be applied independently of legality testing
7581
7582                 /* Check for rep-draws */
7583                 count = 0;
7584                 for(k = forwardMostMove-2;
7585                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7586                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7587                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7588                     k-=2)
7589                 {   int rights=0;
7590                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7591                         /* compare castling rights */
7592                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7593                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7594                                 rights++; /* King lost rights, while rook still had them */
7595                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7596                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7597                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7598                                    rights++; /* but at least one rook lost them */
7599                         }
7600                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7601                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7602                                 rights++;
7603                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7604                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7605                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7606                                    rights++;
7607                         }
7608                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7609                             && appData.drawRepeats > 1) {
7610                              /* adjudicate after user-specified nr of repeats */
7611                              int result = GameIsDrawn;
7612                              char *details = "XBoard adjudication: repetition draw";
7613                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7614                                 // [HGM] xiangqi: check for forbidden perpetuals
7615                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7616                                 for(m=forwardMostMove; m>k; m-=2) {
7617                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7618                                         ourPerpetual = 0; // the current mover did not always check
7619                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7620                                         hisPerpetual = 0; // the opponent did not always check
7621                                 }
7622                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7623                                                                         ourPerpetual, hisPerpetual);
7624                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7625                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7626                                     details = "Xboard adjudication: perpetual checking";
7627                                 } else
7628                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7629                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7630                                 } else
7631                                 // Now check for perpetual chases
7632                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7633                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7634                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7635                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7636                                         static char resdet[MSG_SIZ];
7637                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7638                                         details = resdet;
7639                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7640                                     } else
7641                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7642                                         break; // Abort repetition-checking loop.
7643                                 }
7644                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7645                              }
7646                              if(engineOpponent) {
7647                                SendToProgram("force\n", engineOpponent); // suppress reply
7648                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7649                              }
7650                              GameEnds( result, details, GE_XBOARD );
7651                              return 1;
7652                         }
7653                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7654                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7655                     }
7656                 }
7657
7658                 /* Now we test for 50-move draws. Determine ply count */
7659                 count = forwardMostMove;
7660                 /* look for last irreversble move */
7661                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7662                     count--;
7663                 /* if we hit starting position, add initial plies */
7664                 if( count == backwardMostMove )
7665                     count -= initialRulePlies;
7666                 count = forwardMostMove - count;
7667                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7668                         // adjust reversible move counter for checks in Xiangqi
7669                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7670                         if(i < backwardMostMove) i = backwardMostMove;
7671                         while(i <= forwardMostMove) {
7672                                 lastCheck = inCheck; // check evasion does not count
7673                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7674                                 if(inCheck || lastCheck) count--; // check does not count
7675                                 i++;
7676                         }
7677                 }
7678                 if( count >= 100)
7679                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7680                          /* this is used to judge if draw claims are legal */
7681                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7682                          if(engineOpponent) {
7683                            SendToProgram("force\n", engineOpponent); // suppress reply
7684                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7685                          }
7686                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7687                          return 1;
7688                 }
7689
7690                 /* if draw offer is pending, treat it as a draw claim
7691                  * when draw condition present, to allow engines a way to
7692                  * claim draws before making their move to avoid a race
7693                  * condition occurring after their move
7694                  */
7695                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7696                          char *p = NULL;
7697                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7698                              p = "Draw claim: 50-move rule";
7699                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7700                              p = "Draw claim: 3-fold repetition";
7701                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7702                              p = "Draw claim: insufficient mating material";
7703                          if( p != NULL && canAdjudicate) {
7704                              if(engineOpponent) {
7705                                SendToProgram("force\n", engineOpponent); // suppress reply
7706                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7707                              }
7708                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7709                              return 1;
7710                          }
7711                 }
7712
7713                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7714                     if(engineOpponent) {
7715                       SendToProgram("force\n", engineOpponent); // suppress reply
7716                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7717                     }
7718                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7719                     return 1;
7720                 }
7721         return 0;
7722 }
7723
7724 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7725 {   // [HGM] book: this routine intercepts moves to simulate book replies
7726     char *bookHit = NULL;
7727
7728     //first determine if the incoming move brings opponent into his book
7729     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7730         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7731     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7732     if(bookHit != NULL && !cps->bookSuspend) {
7733         // make sure opponent is not going to reply after receiving move to book position
7734         SendToProgram("force\n", cps);
7735         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7736     }
7737     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7738     // now arrange restart after book miss
7739     if(bookHit) {
7740         // after a book hit we never send 'go', and the code after the call to this routine
7741         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7742         char buf[MSG_SIZ], *move = bookHit;
7743         if(cps->useSAN) {
7744             int fromX, fromY, toX, toY;
7745             char promoChar;
7746             ChessMove moveType;
7747             move = buf + 30;
7748             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7749                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7750                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7751                                     PosFlags(forwardMostMove),
7752                                     fromY, fromX, toY, toX, promoChar, move);
7753             } else {
7754                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7755                 bookHit = NULL;
7756             }
7757         }
7758         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7759         SendToProgram(buf, cps);
7760         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7761     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7762         SendToProgram("go\n", cps);
7763         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7764     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7765         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7766             SendToProgram("go\n", cps);
7767         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7768     }
7769     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7770 }
7771
7772 char *savedMessage;
7773 ChessProgramState *savedState;
7774 void DeferredBookMove(void)
7775 {
7776         if(savedState->lastPing != savedState->lastPong)
7777                     ScheduleDelayedEvent(DeferredBookMove, 10);
7778         else
7779         HandleMachineMove(savedMessage, savedState);
7780 }
7781
7782 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7783
7784 void
7785 HandleMachineMove(message, cps)
7786      char *message;
7787      ChessProgramState *cps;
7788 {
7789     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7790     char realname[MSG_SIZ];
7791     int fromX, fromY, toX, toY;
7792     ChessMove moveType;
7793     char promoChar;
7794     char *p, *pv=buf1;
7795     int machineWhite;
7796     char *bookHit;
7797
7798     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7799         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7800         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7801             DisplayError(_("Invalid pairing from pairing engine"), 0);
7802             return;
7803         }
7804         pairingReceived = 1;
7805         NextMatchGame();
7806         return; // Skim the pairing messages here.
7807     }
7808
7809     cps->userError = 0;
7810
7811 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7812     /*
7813      * Kludge to ignore BEL characters
7814      */
7815     while (*message == '\007') message++;
7816
7817     /*
7818      * [HGM] engine debug message: ignore lines starting with '#' character
7819      */
7820     if(cps->debug && *message == '#') return;
7821
7822     /*
7823      * Look for book output
7824      */
7825     if (cps == &first && bookRequested) {
7826         if (message[0] == '\t' || message[0] == ' ') {
7827             /* Part of the book output is here; append it */
7828             strcat(bookOutput, message);
7829             strcat(bookOutput, "  \n");
7830             return;
7831         } else if (bookOutput[0] != NULLCHAR) {
7832             /* All of book output has arrived; display it */
7833             char *p = bookOutput;
7834             while (*p != NULLCHAR) {
7835                 if (*p == '\t') *p = ' ';
7836                 p++;
7837             }
7838             DisplayInformation(bookOutput);
7839             bookRequested = FALSE;
7840             /* Fall through to parse the current output */
7841         }
7842     }
7843
7844     /*
7845      * Look for machine move.
7846      */
7847     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7848         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7849     {
7850         /* This method is only useful on engines that support ping */
7851         if (cps->lastPing != cps->lastPong) {
7852           if (gameMode == BeginningOfGame) {
7853             /* Extra move from before last new; ignore */
7854             if (appData.debugMode) {
7855                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7856             }
7857           } else {
7858             if (appData.debugMode) {
7859                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7860                         cps->which, gameMode);
7861             }
7862
7863             SendToProgram("undo\n", cps);
7864           }
7865           return;
7866         }
7867
7868         switch (gameMode) {
7869           case BeginningOfGame:
7870             /* Extra move from before last reset; ignore */
7871             if (appData.debugMode) {
7872                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7873             }
7874             return;
7875
7876           case EndOfGame:
7877           case IcsIdle:
7878           default:
7879             /* Extra move after we tried to stop.  The mode test is
7880                not a reliable way of detecting this problem, but it's
7881                the best we can do on engines that don't support ping.
7882             */
7883             if (appData.debugMode) {
7884                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7885                         cps->which, gameMode);
7886             }
7887             SendToProgram("undo\n", cps);
7888             return;
7889
7890           case MachinePlaysWhite:
7891           case IcsPlayingWhite:
7892             machineWhite = TRUE;
7893             break;
7894
7895           case MachinePlaysBlack:
7896           case IcsPlayingBlack:
7897             machineWhite = FALSE;
7898             break;
7899
7900           case TwoMachinesPlay:
7901             machineWhite = (cps->twoMachinesColor[0] == 'w');
7902             break;
7903         }
7904         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7905             if (appData.debugMode) {
7906                 fprintf(debugFP,
7907                         "Ignoring move out of turn by %s, gameMode %d"
7908                         ", forwardMost %d\n",
7909                         cps->which, gameMode, forwardMostMove);
7910             }
7911             return;
7912         }
7913
7914     if (appData.debugMode) { int f = forwardMostMove;
7915         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7916                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7917                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7918     }
7919         if(cps->alphaRank) AlphaRank(machineMove, 4);
7920         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7921                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7922             /* Machine move could not be parsed; ignore it. */
7923           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7924                     machineMove, _(cps->which));
7925             DisplayError(buf1, 0);
7926             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7927                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7928             if (gameMode == TwoMachinesPlay) {
7929               GameEnds(machineWhite ? BlackWins : WhiteWins,
7930                        buf1, GE_XBOARD);
7931             }
7932             return;
7933         }
7934
7935         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7936         /* So we have to redo legality test with true e.p. status here,  */
7937         /* to make sure an illegal e.p. capture does not slip through,   */
7938         /* to cause a forfeit on a justified illegal-move complaint      */
7939         /* of the opponent.                                              */
7940         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7941            ChessMove moveType;
7942            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7943                              fromY, fromX, toY, toX, promoChar);
7944             if (appData.debugMode) {
7945                 int i;
7946                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7947                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7948                 fprintf(debugFP, "castling rights\n");
7949             }
7950             if(moveType == IllegalMove) {
7951               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7952                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7953                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7954                            buf1, GE_XBOARD);
7955                 return;
7956            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7957            /* [HGM] Kludge to handle engines that send FRC-style castling
7958               when they shouldn't (like TSCP-Gothic) */
7959            switch(moveType) {
7960              case WhiteASideCastleFR:
7961              case BlackASideCastleFR:
7962                toX+=2;
7963                currentMoveString[2]++;
7964                break;
7965              case WhiteHSideCastleFR:
7966              case BlackHSideCastleFR:
7967                toX--;
7968                currentMoveString[2]--;
7969                break;
7970              default: ; // nothing to do, but suppresses warning of pedantic compilers
7971            }
7972         }
7973         hintRequested = FALSE;
7974         lastHint[0] = NULLCHAR;
7975         bookRequested = FALSE;
7976         /* Program may be pondering now */
7977         cps->maybeThinking = TRUE;
7978         if (cps->sendTime == 2) cps->sendTime = 1;
7979         if (cps->offeredDraw) cps->offeredDraw--;
7980
7981         /* [AS] Save move info*/
7982         pvInfoList[ forwardMostMove ].score = programStats.score;
7983         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7984         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7985
7986         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7987
7988         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7989         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7990             int count = 0;
7991
7992             while( count < adjudicateLossPlies ) {
7993                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7994
7995                 if( count & 1 ) {
7996                     score = -score; /* Flip score for winning side */
7997                 }
7998
7999                 if( score > adjudicateLossThreshold ) {
8000                     break;
8001                 }
8002
8003                 count++;
8004             }
8005
8006             if( count >= adjudicateLossPlies ) {
8007                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8008
8009                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8010                     "Xboard adjudication",
8011                     GE_XBOARD );
8012
8013                 return;
8014             }
8015         }
8016
8017         if(Adjudicate(cps)) {
8018             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8019             return; // [HGM] adjudicate: for all automatic game ends
8020         }
8021
8022 #if ZIPPY
8023         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8024             first.initDone) {
8025           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8026                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8027                 SendToICS("draw ");
8028                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8029           }
8030           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8031           ics_user_moved = 1;
8032           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8033                 char buf[3*MSG_SIZ];
8034
8035                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8036                         programStats.score / 100.,
8037                         programStats.depth,
8038                         programStats.time / 100.,
8039                         (unsigned int)programStats.nodes,
8040                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8041                         programStats.movelist);
8042                 SendToICS(buf);
8043 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8044           }
8045         }
8046 #endif
8047
8048         /* [AS] Clear stats for next move */
8049         ClearProgramStats();
8050         thinkOutput[0] = NULLCHAR;
8051         hiddenThinkOutputState = 0;
8052
8053         bookHit = NULL;
8054         if (gameMode == TwoMachinesPlay) {
8055             /* [HGM] relaying draw offers moved to after reception of move */
8056             /* and interpreting offer as claim if it brings draw condition */
8057             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8058                 SendToProgram("draw\n", cps->other);
8059             }
8060             if (cps->other->sendTime) {
8061                 SendTimeRemaining(cps->other,
8062                                   cps->other->twoMachinesColor[0] == 'w');
8063             }
8064             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8065             if (firstMove && !bookHit) {
8066                 firstMove = FALSE;
8067                 if (cps->other->useColors) {
8068                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8069                 }
8070                 SendToProgram("go\n", cps->other);
8071             }
8072             cps->other->maybeThinking = TRUE;
8073         }
8074
8075         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8076
8077         if (!pausing && appData.ringBellAfterMoves) {
8078             RingBell();
8079         }
8080
8081         /*
8082          * Reenable menu items that were disabled while
8083          * machine was thinking
8084          */
8085         if (gameMode != TwoMachinesPlay)
8086             SetUserThinkingEnables();
8087
8088         // [HGM] book: after book hit opponent has received move and is now in force mode
8089         // force the book reply into it, and then fake that it outputted this move by jumping
8090         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8091         if(bookHit) {
8092                 static char bookMove[MSG_SIZ]; // a bit generous?
8093
8094                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8095                 strcat(bookMove, bookHit);
8096                 message = bookMove;
8097                 cps = cps->other;
8098                 programStats.nodes = programStats.depth = programStats.time =
8099                 programStats.score = programStats.got_only_move = 0;
8100                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8101
8102                 if(cps->lastPing != cps->lastPong) {
8103                     savedMessage = message; // args for deferred call
8104                     savedState = cps;
8105                     ScheduleDelayedEvent(DeferredBookMove, 10);
8106                     return;
8107                 }
8108                 goto FakeBookMove;
8109         }
8110
8111         return;
8112     }
8113
8114     /* Set special modes for chess engines.  Later something general
8115      *  could be added here; for now there is just one kludge feature,
8116      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8117      *  when "xboard" is given as an interactive command.
8118      */
8119     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8120         cps->useSigint = FALSE;
8121         cps->useSigterm = FALSE;
8122     }
8123     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8124       ParseFeatures(message+8, cps);
8125       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8126     }
8127
8128     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8129                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8130       int dummy, s=6; char buf[MSG_SIZ];
8131       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8132       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8133       if(startedFromSetupPosition) return;
8134       ParseFEN(boards[0], &dummy, message+s);
8135       DrawPosition(TRUE, boards[0]);
8136       startedFromSetupPosition = TRUE;
8137       return;
8138     }
8139     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8140      * want this, I was asked to put it in, and obliged.
8141      */
8142     if (!strncmp(message, "setboard ", 9)) {
8143         Board initial_position;
8144
8145         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8146
8147         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8148             DisplayError(_("Bad FEN received from engine"), 0);
8149             return ;
8150         } else {
8151            Reset(TRUE, FALSE);
8152            CopyBoard(boards[0], initial_position);
8153            initialRulePlies = FENrulePlies;
8154            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8155            else gameMode = MachinePlaysBlack;
8156            DrawPosition(FALSE, boards[currentMove]);
8157         }
8158         return;
8159     }
8160
8161     /*
8162      * Look for communication commands
8163      */
8164     if (!strncmp(message, "telluser ", 9)) {
8165         if(message[9] == '\\' && message[10] == '\\')
8166             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8167         PlayTellSound();
8168         DisplayNote(message + 9);
8169         return;
8170     }
8171     if (!strncmp(message, "tellusererror ", 14)) {
8172         cps->userError = 1;
8173         if(message[14] == '\\' && message[15] == '\\')
8174             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8175         PlayTellSound();
8176         DisplayError(message + 14, 0);
8177         return;
8178     }
8179     if (!strncmp(message, "tellopponent ", 13)) {
8180       if (appData.icsActive) {
8181         if (loggedOn) {
8182           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8183           SendToICS(buf1);
8184         }
8185       } else {
8186         DisplayNote(message + 13);
8187       }
8188       return;
8189     }
8190     if (!strncmp(message, "tellothers ", 11)) {
8191       if (appData.icsActive) {
8192         if (loggedOn) {
8193           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8194           SendToICS(buf1);
8195         }
8196       }
8197       return;
8198     }
8199     if (!strncmp(message, "tellall ", 8)) {
8200       if (appData.icsActive) {
8201         if (loggedOn) {
8202           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8203           SendToICS(buf1);
8204         }
8205       } else {
8206         DisplayNote(message + 8);
8207       }
8208       return;
8209     }
8210     if (strncmp(message, "warning", 7) == 0) {
8211         /* Undocumented feature, use tellusererror in new code */
8212         DisplayError(message, 0);
8213         return;
8214     }
8215     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8216         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8217         strcat(realname, " query");
8218         AskQuestion(realname, buf2, buf1, cps->pr);
8219         return;
8220     }
8221     /* Commands from the engine directly to ICS.  We don't allow these to be
8222      *  sent until we are logged on. Crafty kibitzes have been known to
8223      *  interfere with the login process.
8224      */
8225     if (loggedOn) {
8226         if (!strncmp(message, "tellics ", 8)) {
8227             SendToICS(message + 8);
8228             SendToICS("\n");
8229             return;
8230         }
8231         if (!strncmp(message, "tellicsnoalias ", 15)) {
8232             SendToICS(ics_prefix);
8233             SendToICS(message + 15);
8234             SendToICS("\n");
8235             return;
8236         }
8237         /* The following are for backward compatibility only */
8238         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8239             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8240             SendToICS(ics_prefix);
8241             SendToICS(message);
8242             SendToICS("\n");
8243             return;
8244         }
8245     }
8246     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8247         return;
8248     }
8249     /*
8250      * If the move is illegal, cancel it and redraw the board.
8251      * Also deal with other error cases.  Matching is rather loose
8252      * here to accommodate engines written before the spec.
8253      */
8254     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8255         strncmp(message, "Error", 5) == 0) {
8256         if (StrStr(message, "name") ||
8257             StrStr(message, "rating") || StrStr(message, "?") ||
8258             StrStr(message, "result") || StrStr(message, "board") ||
8259             StrStr(message, "bk") || StrStr(message, "computer") ||
8260             StrStr(message, "variant") || StrStr(message, "hint") ||
8261             StrStr(message, "random") || StrStr(message, "depth") ||
8262             StrStr(message, "accepted")) {
8263             return;
8264         }
8265         if (StrStr(message, "protover")) {
8266           /* Program is responding to input, so it's apparently done
8267              initializing, and this error message indicates it is
8268              protocol version 1.  So we don't need to wait any longer
8269              for it to initialize and send feature commands. */
8270           FeatureDone(cps, 1);
8271           cps->protocolVersion = 1;
8272           return;
8273         }
8274         cps->maybeThinking = FALSE;
8275
8276         if (StrStr(message, "draw")) {
8277             /* Program doesn't have "draw" command */
8278             cps->sendDrawOffers = 0;
8279             return;
8280         }
8281         if (cps->sendTime != 1 &&
8282             (StrStr(message, "time") || StrStr(message, "otim"))) {
8283           /* Program apparently doesn't have "time" or "otim" command */
8284           cps->sendTime = 0;
8285           return;
8286         }
8287         if (StrStr(message, "analyze")) {
8288             cps->analysisSupport = FALSE;
8289             cps->analyzing = FALSE;
8290 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8291             EditGameEvent(); // [HGM] try to preserve loaded game
8292             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8293             DisplayError(buf2, 0);
8294             return;
8295         }
8296         if (StrStr(message, "(no matching move)st")) {
8297           /* Special kludge for GNU Chess 4 only */
8298           cps->stKludge = TRUE;
8299           SendTimeControl(cps, movesPerSession, timeControl,
8300                           timeIncrement, appData.searchDepth,
8301                           searchTime);
8302           return;
8303         }
8304         if (StrStr(message, "(no matching move)sd")) {
8305           /* Special kludge for GNU Chess 4 only */
8306           cps->sdKludge = TRUE;
8307           SendTimeControl(cps, movesPerSession, timeControl,
8308                           timeIncrement, appData.searchDepth,
8309                           searchTime);
8310           return;
8311         }
8312         if (!StrStr(message, "llegal")) {
8313             return;
8314         }
8315         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8316             gameMode == IcsIdle) return;
8317         if (forwardMostMove <= backwardMostMove) return;
8318         if (pausing) PauseEvent();
8319       if(appData.forceIllegal) {
8320             // [HGM] illegal: machine refused move; force position after move into it
8321           SendToProgram("force\n", cps);
8322           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8323                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8324                 // when black is to move, while there might be nothing on a2 or black
8325                 // might already have the move. So send the board as if white has the move.
8326                 // But first we must change the stm of the engine, as it refused the last move
8327                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8328                 if(WhiteOnMove(forwardMostMove)) {
8329                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8330                     SendBoard(cps, forwardMostMove); // kludgeless board
8331                 } else {
8332                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8333                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8334                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8335                 }
8336           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8337             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8338                  gameMode == TwoMachinesPlay)
8339               SendToProgram("go\n", cps);
8340             return;
8341       } else
8342         if (gameMode == PlayFromGameFile) {
8343             /* Stop reading this game file */
8344             gameMode = EditGame;
8345             ModeHighlight();
8346         }
8347         /* [HGM] illegal-move claim should forfeit game when Xboard */
8348         /* only passes fully legal moves                            */
8349         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8350             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8351                                 "False illegal-move claim", GE_XBOARD );
8352             return; // do not take back move we tested as valid
8353         }
8354         currentMove = forwardMostMove-1;
8355         DisplayMove(currentMove-1); /* before DisplayMoveError */
8356         SwitchClocks(forwardMostMove-1); // [HGM] race
8357         DisplayBothClocks();
8358         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8359                 parseList[currentMove], _(cps->which));
8360         DisplayMoveError(buf1);
8361         DrawPosition(FALSE, boards[currentMove]);
8362         return;
8363     }
8364     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8365         /* Program has a broken "time" command that
8366            outputs a string not ending in newline.
8367            Don't use it. */
8368         cps->sendTime = 0;
8369     }
8370
8371     /*
8372      * If chess program startup fails, exit with an error message.
8373      * Attempts to recover here are futile.
8374      */
8375     if ((StrStr(message, "unknown host") != NULL)
8376         || (StrStr(message, "No remote directory") != NULL)
8377         || (StrStr(message, "not found") != NULL)
8378         || (StrStr(message, "No such file") != NULL)
8379         || (StrStr(message, "can't alloc") != NULL)
8380         || (StrStr(message, "Permission denied") != NULL)) {
8381
8382         cps->maybeThinking = FALSE;
8383         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8384                 _(cps->which), cps->program, cps->host, message);
8385         RemoveInputSource(cps->isr);
8386         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8387             if(cps == &first) appData.noChessProgram = TRUE;
8388             DisplayError(buf1, 0);
8389         }
8390         return;
8391     }
8392
8393     /*
8394      * Look for hint output
8395      */
8396     if (sscanf(message, "Hint: %s", buf1) == 1) {
8397         if (cps == &first && hintRequested) {
8398             hintRequested = FALSE;
8399             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8400                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8401                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8402                                     PosFlags(forwardMostMove),
8403                                     fromY, fromX, toY, toX, promoChar, buf1);
8404                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8405                 DisplayInformation(buf2);
8406             } else {
8407                 /* Hint move could not be parsed!? */
8408               snprintf(buf2, sizeof(buf2),
8409                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8410                         buf1, _(cps->which));
8411                 DisplayError(buf2, 0);
8412             }
8413         } else {
8414           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8415         }
8416         return;
8417     }
8418
8419     /*
8420      * Ignore other messages if game is not in progress
8421      */
8422     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8423         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8424
8425     /*
8426      * look for win, lose, draw, or draw offer
8427      */
8428     if (strncmp(message, "1-0", 3) == 0) {
8429         char *p, *q, *r = "";
8430         p = strchr(message, '{');
8431         if (p) {
8432             q = strchr(p, '}');
8433             if (q) {
8434                 *q = NULLCHAR;
8435                 r = p + 1;
8436             }
8437         }
8438         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8439         return;
8440     } else if (strncmp(message, "0-1", 3) == 0) {
8441         char *p, *q, *r = "";
8442         p = strchr(message, '{');
8443         if (p) {
8444             q = strchr(p, '}');
8445             if (q) {
8446                 *q = NULLCHAR;
8447                 r = p + 1;
8448             }
8449         }
8450         /* Kludge for Arasan 4.1 bug */
8451         if (strcmp(r, "Black resigns") == 0) {
8452             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8453             return;
8454         }
8455         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8456         return;
8457     } else if (strncmp(message, "1/2", 3) == 0) {
8458         char *p, *q, *r = "";
8459         p = strchr(message, '{');
8460         if (p) {
8461             q = strchr(p, '}');
8462             if (q) {
8463                 *q = NULLCHAR;
8464                 r = p + 1;
8465             }
8466         }
8467
8468         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8469         return;
8470
8471     } else if (strncmp(message, "White resign", 12) == 0) {
8472         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8473         return;
8474     } else if (strncmp(message, "Black resign", 12) == 0) {
8475         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8476         return;
8477     } else if (strncmp(message, "White matches", 13) == 0 ||
8478                strncmp(message, "Black matches", 13) == 0   ) {
8479         /* [HGM] ignore GNUShogi noises */
8480         return;
8481     } else if (strncmp(message, "White", 5) == 0 &&
8482                message[5] != '(' &&
8483                StrStr(message, "Black") == NULL) {
8484         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8485         return;
8486     } else if (strncmp(message, "Black", 5) == 0 &&
8487                message[5] != '(') {
8488         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8489         return;
8490     } else if (strcmp(message, "resign") == 0 ||
8491                strcmp(message, "computer resigns") == 0) {
8492         switch (gameMode) {
8493           case MachinePlaysBlack:
8494           case IcsPlayingBlack:
8495             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8496             break;
8497           case MachinePlaysWhite:
8498           case IcsPlayingWhite:
8499             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8500             break;
8501           case TwoMachinesPlay:
8502             if (cps->twoMachinesColor[0] == 'w')
8503               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8504             else
8505               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8506             break;
8507           default:
8508             /* can't happen */
8509             break;
8510         }
8511         return;
8512     } else if (strncmp(message, "opponent mates", 14) == 0) {
8513         switch (gameMode) {
8514           case MachinePlaysBlack:
8515           case IcsPlayingBlack:
8516             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8517             break;
8518           case MachinePlaysWhite:
8519           case IcsPlayingWhite:
8520             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8521             break;
8522           case TwoMachinesPlay:
8523             if (cps->twoMachinesColor[0] == 'w')
8524               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8525             else
8526               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8527             break;
8528           default:
8529             /* can't happen */
8530             break;
8531         }
8532         return;
8533     } else if (strncmp(message, "computer mates", 14) == 0) {
8534         switch (gameMode) {
8535           case MachinePlaysBlack:
8536           case IcsPlayingBlack:
8537             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8538             break;
8539           case MachinePlaysWhite:
8540           case IcsPlayingWhite:
8541             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8542             break;
8543           case TwoMachinesPlay:
8544             if (cps->twoMachinesColor[0] == 'w')
8545               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8546             else
8547               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8548             break;
8549           default:
8550             /* can't happen */
8551             break;
8552         }
8553         return;
8554     } else if (strncmp(message, "checkmate", 9) == 0) {
8555         if (WhiteOnMove(forwardMostMove)) {
8556             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8557         } else {
8558             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8559         }
8560         return;
8561     } else if (strstr(message, "Draw") != NULL ||
8562                strstr(message, "game is a draw") != NULL) {
8563         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8564         return;
8565     } else if (strstr(message, "offer") != NULL &&
8566                strstr(message, "draw") != NULL) {
8567 #if ZIPPY
8568         if (appData.zippyPlay && first.initDone) {
8569             /* Relay offer to ICS */
8570             SendToICS(ics_prefix);
8571             SendToICS("draw\n");
8572         }
8573 #endif
8574         cps->offeredDraw = 2; /* valid until this engine moves twice */
8575         if (gameMode == TwoMachinesPlay) {
8576             if (cps->other->offeredDraw) {
8577                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8578             /* [HGM] in two-machine mode we delay relaying draw offer      */
8579             /* until after we also have move, to see if it is really claim */
8580             }
8581         } else if (gameMode == MachinePlaysWhite ||
8582                    gameMode == MachinePlaysBlack) {
8583           if (userOfferedDraw) {
8584             DisplayInformation(_("Machine accepts your draw offer"));
8585             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8586           } else {
8587             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8588           }
8589         }
8590     }
8591
8592
8593     /*
8594      * Look for thinking output
8595      */
8596     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8597           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8598                                 ) {
8599         int plylev, mvleft, mvtot, curscore, time;
8600         char mvname[MOVE_LEN];
8601         u64 nodes; // [DM]
8602         char plyext;
8603         int ignore = FALSE;
8604         int prefixHint = FALSE;
8605         mvname[0] = NULLCHAR;
8606
8607         switch (gameMode) {
8608           case MachinePlaysBlack:
8609           case IcsPlayingBlack:
8610             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8611             break;
8612           case MachinePlaysWhite:
8613           case IcsPlayingWhite:
8614             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8615             break;
8616           case AnalyzeMode:
8617           case AnalyzeFile:
8618             break;
8619           case IcsObserving: /* [DM] icsEngineAnalyze */
8620             if (!appData.icsEngineAnalyze) ignore = TRUE;
8621             break;
8622           case TwoMachinesPlay:
8623             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8624                 ignore = TRUE;
8625             }
8626             break;
8627           default:
8628             ignore = TRUE;
8629             break;
8630         }
8631
8632         if (!ignore) {
8633             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8634             buf1[0] = NULLCHAR;
8635             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8636                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8637
8638                 if (plyext != ' ' && plyext != '\t') {
8639                     time *= 100;
8640                 }
8641
8642                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8643                 if( cps->scoreIsAbsolute &&
8644                     ( gameMode == MachinePlaysBlack ||
8645                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8646                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8647                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8648                      !WhiteOnMove(currentMove)
8649                     ) )
8650                 {
8651                     curscore = -curscore;
8652                 }
8653
8654                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8655
8656                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8657                         char buf[MSG_SIZ];
8658                         FILE *f;
8659                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8660                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8661                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8662                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8663                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8664                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8665                                 fclose(f);
8666                         } else DisplayError(_("failed writing PV"), 0);
8667                 }
8668
8669                 tempStats.depth = plylev;
8670                 tempStats.nodes = nodes;
8671                 tempStats.time = time;
8672                 tempStats.score = curscore;
8673                 tempStats.got_only_move = 0;
8674
8675                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8676                         int ticklen;
8677
8678                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8679                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8680                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8681                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8682                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8683                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8684                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8685                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8686                 }
8687
8688                 /* Buffer overflow protection */
8689                 if (pv[0] != NULLCHAR) {
8690                     if (strlen(pv) >= sizeof(tempStats.movelist)
8691                         && appData.debugMode) {
8692                         fprintf(debugFP,
8693                                 "PV is too long; using the first %u bytes.\n",
8694                                 (unsigned) sizeof(tempStats.movelist) - 1);
8695                     }
8696
8697                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8698                 } else {
8699                     sprintf(tempStats.movelist, " no PV\n");
8700                 }
8701
8702                 if (tempStats.seen_stat) {
8703                     tempStats.ok_to_send = 1;
8704                 }
8705
8706                 if (strchr(tempStats.movelist, '(') != NULL) {
8707                     tempStats.line_is_book = 1;
8708                     tempStats.nr_moves = 0;
8709                     tempStats.moves_left = 0;
8710                 } else {
8711                     tempStats.line_is_book = 0;
8712                 }
8713
8714                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8715                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8716
8717                 SendProgramStatsToFrontend( cps, &tempStats );
8718
8719                 /*
8720                     [AS] Protect the thinkOutput buffer from overflow... this
8721                     is only useful if buf1 hasn't overflowed first!
8722                 */
8723                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8724                          plylev,
8725                          (gameMode == TwoMachinesPlay ?
8726                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8727                          ((double) curscore) / 100.0,
8728                          prefixHint ? lastHint : "",
8729                          prefixHint ? " " : "" );
8730
8731                 if( buf1[0] != NULLCHAR ) {
8732                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8733
8734                     if( strlen(pv) > max_len ) {
8735                         if( appData.debugMode) {
8736                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8737                         }
8738                         pv[max_len+1] = '\0';
8739                     }
8740
8741                     strcat( thinkOutput, pv);
8742                 }
8743
8744                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8745                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8746                     DisplayMove(currentMove - 1);
8747                 }
8748                 return;
8749
8750             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8751                 /* crafty (9.25+) says "(only move) <move>"
8752                  * if there is only 1 legal move
8753                  */
8754                 sscanf(p, "(only move) %s", buf1);
8755                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8756                 sprintf(programStats.movelist, "%s (only move)", buf1);
8757                 programStats.depth = 1;
8758                 programStats.nr_moves = 1;
8759                 programStats.moves_left = 1;
8760                 programStats.nodes = 1;
8761                 programStats.time = 1;
8762                 programStats.got_only_move = 1;
8763
8764                 /* Not really, but we also use this member to
8765                    mean "line isn't going to change" (Crafty
8766                    isn't searching, so stats won't change) */
8767                 programStats.line_is_book = 1;
8768
8769                 SendProgramStatsToFrontend( cps, &programStats );
8770
8771                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8772                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8773                     DisplayMove(currentMove - 1);
8774                 }
8775                 return;
8776             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8777                               &time, &nodes, &plylev, &mvleft,
8778                               &mvtot, mvname) >= 5) {
8779                 /* The stat01: line is from Crafty (9.29+) in response
8780                    to the "." command */
8781                 programStats.seen_stat = 1;
8782                 cps->maybeThinking = TRUE;
8783
8784                 if (programStats.got_only_move || !appData.periodicUpdates)
8785                   return;
8786
8787                 programStats.depth = plylev;
8788                 programStats.time = time;
8789                 programStats.nodes = nodes;
8790                 programStats.moves_left = mvleft;
8791                 programStats.nr_moves = mvtot;
8792                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8793                 programStats.ok_to_send = 1;
8794                 programStats.movelist[0] = '\0';
8795
8796                 SendProgramStatsToFrontend( cps, &programStats );
8797
8798                 return;
8799
8800             } else if (strncmp(message,"++",2) == 0) {
8801                 /* Crafty 9.29+ outputs this */
8802                 programStats.got_fail = 2;
8803                 return;
8804
8805             } else if (strncmp(message,"--",2) == 0) {
8806                 /* Crafty 9.29+ outputs this */
8807                 programStats.got_fail = 1;
8808                 return;
8809
8810             } else if (thinkOutput[0] != NULLCHAR &&
8811                        strncmp(message, "    ", 4) == 0) {
8812                 unsigned message_len;
8813
8814                 p = message;
8815                 while (*p && *p == ' ') p++;
8816
8817                 message_len = strlen( p );
8818
8819                 /* [AS] Avoid buffer overflow */
8820                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8821                     strcat(thinkOutput, " ");
8822                     strcat(thinkOutput, p);
8823                 }
8824
8825                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8826                     strcat(programStats.movelist, " ");
8827                     strcat(programStats.movelist, p);
8828                 }
8829
8830                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8831                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8832                     DisplayMove(currentMove - 1);
8833                 }
8834                 return;
8835             }
8836         }
8837         else {
8838             buf1[0] = NULLCHAR;
8839
8840             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8841                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8842             {
8843                 ChessProgramStats cpstats;
8844
8845                 if (plyext != ' ' && plyext != '\t') {
8846                     time *= 100;
8847                 }
8848
8849                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8850                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8851                     curscore = -curscore;
8852                 }
8853
8854                 cpstats.depth = plylev;
8855                 cpstats.nodes = nodes;
8856                 cpstats.time = time;
8857                 cpstats.score = curscore;
8858                 cpstats.got_only_move = 0;
8859                 cpstats.movelist[0] = '\0';
8860
8861                 if (buf1[0] != NULLCHAR) {
8862                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8863                 }
8864
8865                 cpstats.ok_to_send = 0;
8866                 cpstats.line_is_book = 0;
8867                 cpstats.nr_moves = 0;
8868                 cpstats.moves_left = 0;
8869
8870                 SendProgramStatsToFrontend( cps, &cpstats );
8871             }
8872         }
8873     }
8874 }
8875
8876
8877 /* Parse a game score from the character string "game", and
8878    record it as the history of the current game.  The game
8879    score is NOT assumed to start from the standard position.
8880    The display is not updated in any way.
8881    */
8882 void
8883 ParseGameHistory(game)
8884      char *game;
8885 {
8886     ChessMove moveType;
8887     int fromX, fromY, toX, toY, boardIndex;
8888     char promoChar;
8889     char *p, *q;
8890     char buf[MSG_SIZ];
8891
8892     if (appData.debugMode)
8893       fprintf(debugFP, "Parsing game history: %s\n", game);
8894
8895     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8896     gameInfo.site = StrSave(appData.icsHost);
8897     gameInfo.date = PGNDate();
8898     gameInfo.round = StrSave("-");
8899
8900     /* Parse out names of players */
8901     while (*game == ' ') game++;
8902     p = buf;
8903     while (*game != ' ') *p++ = *game++;
8904     *p = NULLCHAR;
8905     gameInfo.white = StrSave(buf);
8906     while (*game == ' ') game++;
8907     p = buf;
8908     while (*game != ' ' && *game != '\n') *p++ = *game++;
8909     *p = NULLCHAR;
8910     gameInfo.black = StrSave(buf);
8911
8912     /* Parse moves */
8913     boardIndex = blackPlaysFirst ? 1 : 0;
8914     yynewstr(game);
8915     for (;;) {
8916         yyboardindex = boardIndex;
8917         moveType = (ChessMove) Myylex();
8918         switch (moveType) {
8919           case IllegalMove:             /* maybe suicide chess, etc. */
8920   if (appData.debugMode) {
8921     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8922     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8923     setbuf(debugFP, NULL);
8924   }
8925           case WhitePromotion:
8926           case BlackPromotion:
8927           case WhiteNonPromotion:
8928           case BlackNonPromotion:
8929           case NormalMove:
8930           case WhiteCapturesEnPassant:
8931           case BlackCapturesEnPassant:
8932           case WhiteKingSideCastle:
8933           case WhiteQueenSideCastle:
8934           case BlackKingSideCastle:
8935           case BlackQueenSideCastle:
8936           case WhiteKingSideCastleWild:
8937           case WhiteQueenSideCastleWild:
8938           case BlackKingSideCastleWild:
8939           case BlackQueenSideCastleWild:
8940           /* PUSH Fabien */
8941           case WhiteHSideCastleFR:
8942           case WhiteASideCastleFR:
8943           case BlackHSideCastleFR:
8944           case BlackASideCastleFR:
8945           /* POP Fabien */
8946             fromX = currentMoveString[0] - AAA;
8947             fromY = currentMoveString[1] - ONE;
8948             toX = currentMoveString[2] - AAA;
8949             toY = currentMoveString[3] - ONE;
8950             promoChar = currentMoveString[4];
8951             break;
8952           case WhiteDrop:
8953           case BlackDrop:
8954             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8955             fromX = moveType == WhiteDrop ?
8956               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8957             (int) CharToPiece(ToLower(currentMoveString[0]));
8958             fromY = DROP_RANK;
8959             toX = currentMoveString[2] - AAA;
8960             toY = currentMoveString[3] - ONE;
8961             promoChar = NULLCHAR;
8962             break;
8963           case AmbiguousMove:
8964             /* bug? */
8965             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8966   if (appData.debugMode) {
8967     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8968     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8969     setbuf(debugFP, NULL);
8970   }
8971             DisplayError(buf, 0);
8972             return;
8973           case ImpossibleMove:
8974             /* bug? */
8975             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8976   if (appData.debugMode) {
8977     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8978     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8979     setbuf(debugFP, NULL);
8980   }
8981             DisplayError(buf, 0);
8982             return;
8983           case EndOfFile:
8984             if (boardIndex < backwardMostMove) {
8985                 /* Oops, gap.  How did that happen? */
8986                 DisplayError(_("Gap in move list"), 0);
8987                 return;
8988             }
8989             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8990             if (boardIndex > forwardMostMove) {
8991                 forwardMostMove = boardIndex;
8992             }
8993             return;
8994           case ElapsedTime:
8995             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8996                 strcat(parseList[boardIndex-1], " ");
8997                 strcat(parseList[boardIndex-1], yy_text);
8998             }
8999             continue;
9000           case Comment:
9001           case PGNTag:
9002           case NAG:
9003           default:
9004             /* ignore */
9005             continue;
9006           case WhiteWins:
9007           case BlackWins:
9008           case GameIsDrawn:
9009           case GameUnfinished:
9010             if (gameMode == IcsExamining) {
9011                 if (boardIndex < backwardMostMove) {
9012                     /* Oops, gap.  How did that happen? */
9013                     return;
9014                 }
9015                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9016                 return;
9017             }
9018             gameInfo.result = moveType;
9019             p = strchr(yy_text, '{');
9020             if (p == NULL) p = strchr(yy_text, '(');
9021             if (p == NULL) {
9022                 p = yy_text;
9023                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9024             } else {
9025                 q = strchr(p, *p == '{' ? '}' : ')');
9026                 if (q != NULL) *q = NULLCHAR;
9027                 p++;
9028             }
9029             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9030             gameInfo.resultDetails = StrSave(p);
9031             continue;
9032         }
9033         if (boardIndex >= forwardMostMove &&
9034             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9035             backwardMostMove = blackPlaysFirst ? 1 : 0;
9036             return;
9037         }
9038         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9039                                  fromY, fromX, toY, toX, promoChar,
9040                                  parseList[boardIndex]);
9041         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9042         /* currentMoveString is set as a side-effect of yylex */
9043         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9044         strcat(moveList[boardIndex], "\n");
9045         boardIndex++;
9046         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9047         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9048           case MT_NONE:
9049           case MT_STALEMATE:
9050           default:
9051             break;
9052           case MT_CHECK:
9053             if(gameInfo.variant != VariantShogi)
9054                 strcat(parseList[boardIndex - 1], "+");
9055             break;
9056           case MT_CHECKMATE:
9057           case MT_STAINMATE:
9058             strcat(parseList[boardIndex - 1], "#");
9059             break;
9060         }
9061     }
9062 }
9063
9064
9065 /* Apply a move to the given board  */
9066 void
9067 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
9068      int fromX, fromY, toX, toY;
9069      int promoChar;
9070      Board board;
9071 {
9072   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9073   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9074
9075     /* [HGM] compute & store e.p. status and castling rights for new position */
9076     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9077
9078       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9079       oldEP = (signed char)board[EP_STATUS];
9080       board[EP_STATUS] = EP_NONE;
9081
9082   if (fromY == DROP_RANK) {
9083         /* must be first */
9084         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9085             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9086             return;
9087         }
9088         piece = board[toY][toX] = (ChessSquare) fromX;
9089   } else {
9090       int i;
9091
9092       if( board[toY][toX] != EmptySquare )
9093            board[EP_STATUS] = EP_CAPTURE;
9094
9095       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9096            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9097                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9098       } else
9099       if( board[fromY][fromX] == WhitePawn ) {
9100            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9101                board[EP_STATUS] = EP_PAWN_MOVE;
9102            if( toY-fromY==2) {
9103                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9104                         gameInfo.variant != VariantBerolina || toX < fromX)
9105                       board[EP_STATUS] = toX | berolina;
9106                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9107                         gameInfo.variant != VariantBerolina || toX > fromX)
9108                       board[EP_STATUS] = toX;
9109            }
9110       } else
9111       if( board[fromY][fromX] == BlackPawn ) {
9112            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9113                board[EP_STATUS] = EP_PAWN_MOVE;
9114            if( toY-fromY== -2) {
9115                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9116                         gameInfo.variant != VariantBerolina || toX < fromX)
9117                       board[EP_STATUS] = toX | berolina;
9118                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9119                         gameInfo.variant != VariantBerolina || toX > fromX)
9120                       board[EP_STATUS] = toX;
9121            }
9122        }
9123
9124        for(i=0; i<nrCastlingRights; i++) {
9125            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9126               board[CASTLING][i] == toX   && castlingRank[i] == toY
9127              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9128        }
9129
9130      if (fromX == toX && fromY == toY) return;
9131
9132      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9133      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9134      if(gameInfo.variant == VariantKnightmate)
9135          king += (int) WhiteUnicorn - (int) WhiteKing;
9136
9137     /* Code added by Tord: */
9138     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9139     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9140         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9141       board[fromY][fromX] = EmptySquare;
9142       board[toY][toX] = EmptySquare;
9143       if((toX > fromX) != (piece == WhiteRook)) {
9144         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9145       } else {
9146         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9147       }
9148     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9149                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9150       board[fromY][fromX] = EmptySquare;
9151       board[toY][toX] = EmptySquare;
9152       if((toX > fromX) != (piece == BlackRook)) {
9153         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9154       } else {
9155         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9156       }
9157     /* End of code added by Tord */
9158
9159     } else if (board[fromY][fromX] == king
9160         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9161         && toY == fromY && toX > fromX+1) {
9162         board[fromY][fromX] = EmptySquare;
9163         board[toY][toX] = king;
9164         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9165         board[fromY][BOARD_RGHT-1] = EmptySquare;
9166     } else if (board[fromY][fromX] == king
9167         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9168                && toY == fromY && toX < fromX-1) {
9169         board[fromY][fromX] = EmptySquare;
9170         board[toY][toX] = king;
9171         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9172         board[fromY][BOARD_LEFT] = EmptySquare;
9173     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9174                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9175                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9176                ) {
9177         /* white pawn promotion */
9178         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9179         if(gameInfo.variant==VariantBughouse ||
9180            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9181             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9182         board[fromY][fromX] = EmptySquare;
9183     } else if ((fromY >= BOARD_HEIGHT>>1)
9184                && (toX != fromX)
9185                && gameInfo.variant != VariantXiangqi
9186                && gameInfo.variant != VariantBerolina
9187                && (board[fromY][fromX] == WhitePawn)
9188                && (board[toY][toX] == EmptySquare)) {
9189         board[fromY][fromX] = EmptySquare;
9190         board[toY][toX] = WhitePawn;
9191         captured = board[toY - 1][toX];
9192         board[toY - 1][toX] = EmptySquare;
9193     } else if ((fromY == BOARD_HEIGHT-4)
9194                && (toX == fromX)
9195                && gameInfo.variant == VariantBerolina
9196                && (board[fromY][fromX] == WhitePawn)
9197                && (board[toY][toX] == EmptySquare)) {
9198         board[fromY][fromX] = EmptySquare;
9199         board[toY][toX] = WhitePawn;
9200         if(oldEP & EP_BEROLIN_A) {
9201                 captured = board[fromY][fromX-1];
9202                 board[fromY][fromX-1] = EmptySquare;
9203         }else{  captured = board[fromY][fromX+1];
9204                 board[fromY][fromX+1] = EmptySquare;
9205         }
9206     } else if (board[fromY][fromX] == king
9207         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9208                && toY == fromY && toX > fromX+1) {
9209         board[fromY][fromX] = EmptySquare;
9210         board[toY][toX] = king;
9211         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9212         board[fromY][BOARD_RGHT-1] = EmptySquare;
9213     } else if (board[fromY][fromX] == king
9214         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9215                && toY == fromY && toX < fromX-1) {
9216         board[fromY][fromX] = EmptySquare;
9217         board[toY][toX] = king;
9218         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9219         board[fromY][BOARD_LEFT] = EmptySquare;
9220     } else if (fromY == 7 && fromX == 3
9221                && board[fromY][fromX] == BlackKing
9222                && toY == 7 && toX == 5) {
9223         board[fromY][fromX] = EmptySquare;
9224         board[toY][toX] = BlackKing;
9225         board[fromY][7] = EmptySquare;
9226         board[toY][4] = BlackRook;
9227     } else if (fromY == 7 && fromX == 3
9228                && board[fromY][fromX] == BlackKing
9229                && toY == 7 && toX == 1) {
9230         board[fromY][fromX] = EmptySquare;
9231         board[toY][toX] = BlackKing;
9232         board[fromY][0] = EmptySquare;
9233         board[toY][2] = BlackRook;
9234     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9235                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9236                && toY < promoRank && promoChar
9237                ) {
9238         /* black pawn promotion */
9239         board[toY][toX] = CharToPiece(ToLower(promoChar));
9240         if(gameInfo.variant==VariantBughouse ||
9241            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9242             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9243         board[fromY][fromX] = EmptySquare;
9244     } else if ((fromY < BOARD_HEIGHT>>1)
9245                && (toX != fromX)
9246                && gameInfo.variant != VariantXiangqi
9247                && gameInfo.variant != VariantBerolina
9248                && (board[fromY][fromX] == BlackPawn)
9249                && (board[toY][toX] == EmptySquare)) {
9250         board[fromY][fromX] = EmptySquare;
9251         board[toY][toX] = BlackPawn;
9252         captured = board[toY + 1][toX];
9253         board[toY + 1][toX] = EmptySquare;
9254     } else if ((fromY == 3)
9255                && (toX == fromX)
9256                && gameInfo.variant == VariantBerolina
9257                && (board[fromY][fromX] == BlackPawn)
9258                && (board[toY][toX] == EmptySquare)) {
9259         board[fromY][fromX] = EmptySquare;
9260         board[toY][toX] = BlackPawn;
9261         if(oldEP & EP_BEROLIN_A) {
9262                 captured = board[fromY][fromX-1];
9263                 board[fromY][fromX-1] = EmptySquare;
9264         }else{  captured = board[fromY][fromX+1];
9265                 board[fromY][fromX+1] = EmptySquare;
9266         }
9267     } else {
9268         board[toY][toX] = board[fromY][fromX];
9269         board[fromY][fromX] = EmptySquare;
9270     }
9271   }
9272
9273     if (gameInfo.holdingsWidth != 0) {
9274
9275       /* !!A lot more code needs to be written to support holdings  */
9276       /* [HGM] OK, so I have written it. Holdings are stored in the */
9277       /* penultimate board files, so they are automaticlly stored   */
9278       /* in the game history.                                       */
9279       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9280                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9281         /* Delete from holdings, by decreasing count */
9282         /* and erasing image if necessary            */
9283         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9284         if(p < (int) BlackPawn) { /* white drop */
9285              p -= (int)WhitePawn;
9286                  p = PieceToNumber((ChessSquare)p);
9287              if(p >= gameInfo.holdingsSize) p = 0;
9288              if(--board[p][BOARD_WIDTH-2] <= 0)
9289                   board[p][BOARD_WIDTH-1] = EmptySquare;
9290              if((int)board[p][BOARD_WIDTH-2] < 0)
9291                         board[p][BOARD_WIDTH-2] = 0;
9292         } else {                  /* black drop */
9293              p -= (int)BlackPawn;
9294                  p = PieceToNumber((ChessSquare)p);
9295              if(p >= gameInfo.holdingsSize) p = 0;
9296              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9297                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9298              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9299                         board[BOARD_HEIGHT-1-p][1] = 0;
9300         }
9301       }
9302       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9303           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9304         /* [HGM] holdings: Add to holdings, if holdings exist */
9305         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9306                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9307                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9308         }
9309         p = (int) captured;
9310         if (p >= (int) BlackPawn) {
9311           p -= (int)BlackPawn;
9312           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9313                   /* in Shogi restore piece to its original  first */
9314                   captured = (ChessSquare) (DEMOTED captured);
9315                   p = DEMOTED p;
9316           }
9317           p = PieceToNumber((ChessSquare)p);
9318           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9319           board[p][BOARD_WIDTH-2]++;
9320           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9321         } else {
9322           p -= (int)WhitePawn;
9323           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9324                   captured = (ChessSquare) (DEMOTED captured);
9325                   p = DEMOTED p;
9326           }
9327           p = PieceToNumber((ChessSquare)p);
9328           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9329           board[BOARD_HEIGHT-1-p][1]++;
9330           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9331         }
9332       }
9333     } else if (gameInfo.variant == VariantAtomic) {
9334       if (captured != EmptySquare) {
9335         int y, x;
9336         for (y = toY-1; y <= toY+1; y++) {
9337           for (x = toX-1; x <= toX+1; x++) {
9338             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9339                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9340               board[y][x] = EmptySquare;
9341             }
9342           }
9343         }
9344         board[toY][toX] = EmptySquare;
9345       }
9346     }
9347     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9348         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9349     } else
9350     if(promoChar == '+') {
9351         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9352         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9353     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9354         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9355     }
9356     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9357                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9358         // [HGM] superchess: take promotion piece out of holdings
9359         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9360         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9361             if(!--board[k][BOARD_WIDTH-2])
9362                 board[k][BOARD_WIDTH-1] = EmptySquare;
9363         } else {
9364             if(!--board[BOARD_HEIGHT-1-k][1])
9365                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9366         }
9367     }
9368
9369 }
9370
9371 /* Updates forwardMostMove */
9372 void
9373 MakeMove(fromX, fromY, toX, toY, promoChar)
9374      int fromX, fromY, toX, toY;
9375      int promoChar;
9376 {
9377 //    forwardMostMove++; // [HGM] bare: moved downstream
9378
9379     (void) CoordsToAlgebraic(boards[forwardMostMove],
9380                              PosFlags(forwardMostMove),
9381                              fromY, fromX, toY, toX, promoChar,
9382                              parseList[forwardMostMove]);
9383
9384     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9385         int timeLeft; static int lastLoadFlag=0; int king, piece;
9386         piece = boards[forwardMostMove][fromY][fromX];
9387         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9388         if(gameInfo.variant == VariantKnightmate)
9389             king += (int) WhiteUnicorn - (int) WhiteKing;
9390         if(forwardMostMove == 0) {
9391             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9392                 fprintf(serverMoves, "%s;", UserName());
9393             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9394                 fprintf(serverMoves, "%s;", second.tidy);
9395             fprintf(serverMoves, "%s;", first.tidy);
9396             if(gameMode == MachinePlaysWhite)
9397                 fprintf(serverMoves, "%s;", UserName());
9398             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9399                 fprintf(serverMoves, "%s;", second.tidy);
9400         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9401         lastLoadFlag = loadFlag;
9402         // print base move
9403         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9404         // print castling suffix
9405         if( toY == fromY && piece == king ) {
9406             if(toX-fromX > 1)
9407                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9408             if(fromX-toX >1)
9409                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9410         }
9411         // e.p. suffix
9412         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9413              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9414              boards[forwardMostMove][toY][toX] == EmptySquare
9415              && fromX != toX && fromY != toY)
9416                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9417         // promotion suffix
9418         if(promoChar != NULLCHAR)
9419                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9420         if(!loadFlag) {
9421                 char buf[MOVE_LEN*2], *p; int len;
9422             fprintf(serverMoves, "/%d/%d",
9423                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9424             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9425             else                      timeLeft = blackTimeRemaining/1000;
9426             fprintf(serverMoves, "/%d", timeLeft);
9427                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9428                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9429                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9430             fprintf(serverMoves, "/%s", buf);
9431         }
9432         fflush(serverMoves);
9433     }
9434
9435     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9436         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9437       return;
9438     }
9439     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9440     if (commentList[forwardMostMove+1] != NULL) {
9441         free(commentList[forwardMostMove+1]);
9442         commentList[forwardMostMove+1] = NULL;
9443     }
9444     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9445     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9446     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9447     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9448     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9449     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9450     adjustedClock = FALSE;
9451     gameInfo.result = GameUnfinished;
9452     if (gameInfo.resultDetails != NULL) {
9453         free(gameInfo.resultDetails);
9454         gameInfo.resultDetails = NULL;
9455     }
9456     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9457                               moveList[forwardMostMove - 1]);
9458     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9459       case MT_NONE:
9460       case MT_STALEMATE:
9461       default:
9462         break;
9463       case MT_CHECK:
9464         if(gameInfo.variant != VariantShogi)
9465             strcat(parseList[forwardMostMove - 1], "+");
9466         break;
9467       case MT_CHECKMATE:
9468       case MT_STAINMATE:
9469         strcat(parseList[forwardMostMove - 1], "#");
9470         break;
9471     }
9472     if (appData.debugMode) {
9473         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9474     }
9475
9476 }
9477
9478 /* Updates currentMove if not pausing */
9479 void
9480 ShowMove(fromX, fromY, toX, toY)
9481 {
9482     int instant = (gameMode == PlayFromGameFile) ?
9483         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9484     if(appData.noGUI) return;
9485     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9486         if (!instant) {
9487             if (forwardMostMove == currentMove + 1) {
9488                 AnimateMove(boards[forwardMostMove - 1],
9489                             fromX, fromY, toX, toY);
9490             }
9491             if (appData.highlightLastMove) {
9492                 SetHighlights(fromX, fromY, toX, toY);
9493             }
9494         }
9495         currentMove = forwardMostMove;
9496     }
9497
9498     if (instant) return;
9499
9500     DisplayMove(currentMove - 1);
9501     DrawPosition(FALSE, boards[currentMove]);
9502     DisplayBothClocks();
9503     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9504 }
9505
9506 void SendEgtPath(ChessProgramState *cps)
9507 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9508         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9509
9510         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9511
9512         while(*p) {
9513             char c, *q = name+1, *r, *s;
9514
9515             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9516             while(*p && *p != ',') *q++ = *p++;
9517             *q++ = ':'; *q = 0;
9518             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9519                 strcmp(name, ",nalimov:") == 0 ) {
9520                 // take nalimov path from the menu-changeable option first, if it is defined
9521               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9522                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9523             } else
9524             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9525                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9526                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9527                 s = r = StrStr(s, ":") + 1; // beginning of path info
9528                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9529                 c = *r; *r = 0;             // temporarily null-terminate path info
9530                     *--q = 0;               // strip of trailig ':' from name
9531                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9532                 *r = c;
9533                 SendToProgram(buf,cps);     // send egtbpath command for this format
9534             }
9535             if(*p == ',') p++; // read away comma to position for next format name
9536         }
9537 }
9538
9539 void
9540 InitChessProgram(cps, setup)
9541      ChessProgramState *cps;
9542      int setup; /* [HGM] needed to setup FRC opening position */
9543 {
9544     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9545     if (appData.noChessProgram) return;
9546     hintRequested = FALSE;
9547     bookRequested = FALSE;
9548
9549     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9550     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9551     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9552     if(cps->memSize) { /* [HGM] memory */
9553       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9554         SendToProgram(buf, cps);
9555     }
9556     SendEgtPath(cps); /* [HGM] EGT */
9557     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9558       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9559         SendToProgram(buf, cps);
9560     }
9561
9562     SendToProgram(cps->initString, cps);
9563     if (gameInfo.variant != VariantNormal &&
9564         gameInfo.variant != VariantLoadable
9565         /* [HGM] also send variant if board size non-standard */
9566         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9567                                             ) {
9568       char *v = VariantName(gameInfo.variant);
9569       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9570         /* [HGM] in protocol 1 we have to assume all variants valid */
9571         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9572         DisplayFatalError(buf, 0, 1);
9573         return;
9574       }
9575
9576       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9577       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9578       if( gameInfo.variant == VariantXiangqi )
9579            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9580       if( gameInfo.variant == VariantShogi )
9581            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9582       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9583            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9584       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9585           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9586            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9587       if( gameInfo.variant == VariantCourier )
9588            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9589       if( gameInfo.variant == VariantSuper )
9590            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9591       if( gameInfo.variant == VariantGreat )
9592            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9593       if( gameInfo.variant == VariantSChess )
9594            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9595       if( gameInfo.variant == VariantGrand )
9596            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9597
9598       if(overruled) {
9599         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9600                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9601            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9602            if(StrStr(cps->variants, b) == NULL) {
9603                // specific sized variant not known, check if general sizing allowed
9604                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9605                    if(StrStr(cps->variants, "boardsize") == NULL) {
9606                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9607                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9608                        DisplayFatalError(buf, 0, 1);
9609                        return;
9610                    }
9611                    /* [HGM] here we really should compare with the maximum supported board size */
9612                }
9613            }
9614       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9615       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9616       SendToProgram(buf, cps);
9617     }
9618     currentlyInitializedVariant = gameInfo.variant;
9619
9620     /* [HGM] send opening position in FRC to first engine */
9621     if(setup) {
9622           SendToProgram("force\n", cps);
9623           SendBoard(cps, 0);
9624           /* engine is now in force mode! Set flag to wake it up after first move. */
9625           setboardSpoiledMachineBlack = 1;
9626     }
9627
9628     if (cps->sendICS) {
9629       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9630       SendToProgram(buf, cps);
9631     }
9632     cps->maybeThinking = FALSE;
9633     cps->offeredDraw = 0;
9634     if (!appData.icsActive) {
9635         SendTimeControl(cps, movesPerSession, timeControl,
9636                         timeIncrement, appData.searchDepth,
9637                         searchTime);
9638     }
9639     if (appData.showThinking
9640         // [HGM] thinking: four options require thinking output to be sent
9641         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9642                                 ) {
9643         SendToProgram("post\n", cps);
9644     }
9645     SendToProgram("hard\n", cps);
9646     if (!appData.ponderNextMove) {
9647         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9648            it without being sure what state we are in first.  "hard"
9649            is not a toggle, so that one is OK.
9650          */
9651         SendToProgram("easy\n", cps);
9652     }
9653     if (cps->usePing) {
9654       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9655       SendToProgram(buf, cps);
9656     }
9657     cps->initDone = TRUE;
9658     ClearEngineOutputPane(cps == &second);
9659 }
9660
9661
9662 void
9663 StartChessProgram(cps)
9664      ChessProgramState *cps;
9665 {
9666     char buf[MSG_SIZ];
9667     int err;
9668
9669     if (appData.noChessProgram) return;
9670     cps->initDone = FALSE;
9671
9672     if (strcmp(cps->host, "localhost") == 0) {
9673         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9674     } else if (*appData.remoteShell == NULLCHAR) {
9675         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9676     } else {
9677         if (*appData.remoteUser == NULLCHAR) {
9678           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9679                     cps->program);
9680         } else {
9681           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9682                     cps->host, appData.remoteUser, cps->program);
9683         }
9684         err = StartChildProcess(buf, "", &cps->pr);
9685     }
9686
9687     if (err != 0) {
9688       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9689         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9690         if(cps != &first) return;
9691         appData.noChessProgram = TRUE;
9692         ThawUI();
9693         SetNCPMode();
9694 //      DisplayFatalError(buf, err, 1);
9695 //      cps->pr = NoProc;
9696 //      cps->isr = NULL;
9697         return;
9698     }
9699
9700     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9701     if (cps->protocolVersion > 1) {
9702       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9703       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9704       cps->comboCnt = 0;  //                and values of combo boxes
9705       SendToProgram(buf, cps);
9706     } else {
9707       SendToProgram("xboard\n", cps);
9708     }
9709 }
9710
9711 void
9712 TwoMachinesEventIfReady P((void))
9713 {
9714   static int curMess = 0;
9715   if (first.lastPing != first.lastPong) {
9716     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9717     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9718     return;
9719   }
9720   if (second.lastPing != second.lastPong) {
9721     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9722     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9723     return;
9724   }
9725   DisplayMessage("", ""); curMess = 0;
9726   ThawUI();
9727   TwoMachinesEvent();
9728 }
9729
9730 char *
9731 MakeName(char *template)
9732 {
9733     time_t clock;
9734     struct tm *tm;
9735     static char buf[MSG_SIZ];
9736     char *p = buf;
9737     int i;
9738
9739     clock = time((time_t *)NULL);
9740     tm = localtime(&clock);
9741
9742     while(*p++ = *template++) if(p[-1] == '%') {
9743         switch(*template++) {
9744           case 0:   *p = 0; return buf;
9745           case 'Y': i = tm->tm_year+1900; break;
9746           case 'y': i = tm->tm_year-100; break;
9747           case 'M': i = tm->tm_mon+1; break;
9748           case 'd': i = tm->tm_mday; break;
9749           case 'h': i = tm->tm_hour; break;
9750           case 'm': i = tm->tm_min; break;
9751           case 's': i = tm->tm_sec; break;
9752           default:  i = 0;
9753         }
9754         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9755     }
9756     return buf;
9757 }
9758
9759 int
9760 CountPlayers(char *p)
9761 {
9762     int n = 0;
9763     while(p = strchr(p, '\n')) p++, n++; // count participants
9764     return n;
9765 }
9766
9767 FILE *
9768 WriteTourneyFile(char *results, FILE *f)
9769 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9770     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9771     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9772         // create a file with tournament description
9773         fprintf(f, "-participants {%s}\n", appData.participants);
9774         fprintf(f, "-seedBase %d\n", appData.seedBase);
9775         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9776         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9777         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9778         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9779         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9780         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9781         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9782         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9783         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9784         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9785         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9786         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9787         if(searchTime > 0)
9788                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9789         else {
9790                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9791                 fprintf(f, "-tc %s\n", appData.timeControl);
9792                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9793         }
9794         fprintf(f, "-results \"%s\"\n", results);
9795     }
9796     return f;
9797 }
9798
9799 #define MAXENGINES 1000
9800 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9801
9802 void Substitute(char *participants, int expunge)
9803 {
9804     int i, changed, changes=0, nPlayers=0;
9805     char *p, *q, *r, buf[MSG_SIZ];
9806     if(participants == NULL) return;
9807     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9808     r = p = participants; q = appData.participants;
9809     while(*p && *p == *q) {
9810         if(*p == '\n') r = p+1, nPlayers++;
9811         p++; q++;
9812     }
9813     if(*p) { // difference
9814         while(*p && *p++ != '\n');
9815         while(*q && *q++ != '\n');
9816       changed = nPlayers;
9817         changes = 1 + (strcmp(p, q) != 0);
9818     }
9819     if(changes == 1) { // a single engine mnemonic was changed
9820         q = r; while(*q) nPlayers += (*q++ == '\n');
9821         p = buf; while(*r && (*p = *r++) != '\n') p++;
9822         *p = NULLCHAR;
9823         NamesToList(firstChessProgramNames, command, mnemonic);
9824         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9825         if(mnemonic[i]) { // The substitute is valid
9826             FILE *f;
9827             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9828                 flock(fileno(f), LOCK_EX);
9829                 ParseArgsFromFile(f);
9830                 fseek(f, 0, SEEK_SET);
9831                 FREE(appData.participants); appData.participants = participants;
9832                 if(expunge) { // erase results of replaced engine
9833                     int len = strlen(appData.results), w, b, dummy;
9834                     for(i=0; i<len; i++) {
9835                         Pairing(i, nPlayers, &w, &b, &dummy);
9836                         if((w == changed || b == changed) && appData.results[i] == '*') {
9837                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9838                             fclose(f);
9839                             return;
9840                         }
9841                     }
9842                     for(i=0; i<len; i++) {
9843                         Pairing(i, nPlayers, &w, &b, &dummy);
9844                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9845                     }
9846                 }
9847                 WriteTourneyFile(appData.results, f);
9848                 fclose(f); // release lock
9849                 return;
9850             }
9851         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9852     }
9853     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9854     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9855     free(participants);
9856     return;
9857 }
9858
9859 int
9860 CreateTourney(char *name)
9861 {
9862         FILE *f;
9863         if(matchMode && strcmp(name, appData.tourneyFile)) {
9864              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9865         }
9866         if(name[0] == NULLCHAR) {
9867             if(appData.participants[0])
9868                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9869             return 0;
9870         }
9871         f = fopen(name, "r");
9872         if(f) { // file exists
9873             ASSIGN(appData.tourneyFile, name);
9874             ParseArgsFromFile(f); // parse it
9875         } else {
9876             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9877             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9878                 DisplayError(_("Not enough participants"), 0);
9879                 return 0;
9880             }
9881             ASSIGN(appData.tourneyFile, name);
9882             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9883             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9884         }
9885         fclose(f);
9886         appData.noChessProgram = FALSE;
9887         appData.clockMode = TRUE;
9888         SetGNUMode();
9889         return 1;
9890 }
9891
9892 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9893 {
9894     char buf[MSG_SIZ], *p, *q;
9895     int i=1;
9896     while(*names) {
9897         p = names; q = buf;
9898         while(*p && *p != '\n') *q++ = *p++;
9899         *q = 0;
9900         if(engineList[i]) free(engineList[i]);
9901         engineList[i] = strdup(buf);
9902         if(*p == '\n') p++;
9903         TidyProgramName(engineList[i], "localhost", buf);
9904         if(engineMnemonic[i]) free(engineMnemonic[i]);
9905         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9906             strcat(buf, " (");
9907             sscanf(q + 8, "%s", buf + strlen(buf));
9908             strcat(buf, ")");
9909         }
9910         engineMnemonic[i] = strdup(buf);
9911         names = p; i++;
9912       if(i > MAXENGINES - 2) break;
9913     }
9914     engineList[i] = engineMnemonic[i] = NULL;
9915 }
9916
9917 // following implemented as macro to avoid type limitations
9918 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9919
9920 void SwapEngines(int n)
9921 {   // swap settings for first engine and other engine (so far only some selected options)
9922     int h;
9923     char *p;
9924     if(n == 0) return;
9925     SWAP(directory, p)
9926     SWAP(chessProgram, p)
9927     SWAP(isUCI, h)
9928     SWAP(hasOwnBookUCI, h)
9929     SWAP(protocolVersion, h)
9930     SWAP(reuse, h)
9931     SWAP(scoreIsAbsolute, h)
9932     SWAP(timeOdds, h)
9933     SWAP(logo, p)
9934     SWAP(pgnName, p)
9935     SWAP(pvSAN, h)
9936     SWAP(engOptions, p)
9937 }
9938
9939 void
9940 SetPlayer(int player)
9941 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9942     int i;
9943     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9944     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9945     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9946     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9947     if(mnemonic[i]) {
9948         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9949         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9950         appData.firstHasOwnBookUCI = !appData.defNoBook;
9951         ParseArgsFromString(buf);
9952     }
9953     free(engineName);
9954 }
9955
9956 int
9957 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9958 {   // determine players from game number
9959     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9960
9961     if(appData.tourneyType == 0) {
9962         roundsPerCycle = (nPlayers - 1) | 1;
9963         pairingsPerRound = nPlayers / 2;
9964     } else if(appData.tourneyType > 0) {
9965         roundsPerCycle = nPlayers - appData.tourneyType;
9966         pairingsPerRound = appData.tourneyType;
9967     }
9968     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9969     gamesPerCycle = gamesPerRound * roundsPerCycle;
9970     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9971     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9972     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9973     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9974     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9975     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9976
9977     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9978     if(appData.roundSync) *syncInterval = gamesPerRound;
9979
9980     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9981
9982     if(appData.tourneyType == 0) {
9983         if(curPairing == (nPlayers-1)/2 ) {
9984             *whitePlayer = curRound;
9985             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9986         } else {
9987             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9988             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9989             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9990             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9991         }
9992     } else if(appData.tourneyType > 0) {
9993         *whitePlayer = curPairing;
9994         *blackPlayer = curRound + appData.tourneyType;
9995     }
9996
9997     // take care of white/black alternation per round. 
9998     // For cycles and games this is already taken care of by default, derived from matchGame!
9999     return curRound & 1;
10000 }
10001
10002 int
10003 NextTourneyGame(int nr, int *swapColors)
10004 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10005     char *p, *q;
10006     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10007     FILE *tf;
10008     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10009     tf = fopen(appData.tourneyFile, "r");
10010     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10011     ParseArgsFromFile(tf); fclose(tf);
10012     InitTimeControls(); // TC might be altered from tourney file
10013
10014     nPlayers = CountPlayers(appData.participants); // count participants
10015     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10016     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10017
10018     if(syncInterval) {
10019         p = q = appData.results;
10020         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10021         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10022             DisplayMessage(_("Waiting for other game(s)"),"");
10023             waitingForGame = TRUE;
10024             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10025             return 0;
10026         }
10027         waitingForGame = FALSE;
10028     }
10029
10030     if(appData.tourneyType < 0) {
10031         if(nr>=0 && !pairingReceived) {
10032             char buf[1<<16];
10033             if(pairing.pr == NoProc) {
10034                 if(!appData.pairingEngine[0]) {
10035                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10036                     return 0;
10037                 }
10038                 StartChessProgram(&pairing); // starts the pairing engine
10039             }
10040             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10041             SendToProgram(buf, &pairing);
10042             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10043             SendToProgram(buf, &pairing);
10044             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10045         }
10046         pairingReceived = 0;                              // ... so we continue here 
10047         *swapColors = 0;
10048         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10049         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10050         matchGame = 1; roundNr = nr / syncInterval + 1;
10051     }
10052
10053     if(first.pr != NoProc || second.pr != NoProc) return 1; // engines already loaded
10054
10055     // redefine engines, engine dir, etc.
10056     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10057     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
10058     SwapEngines(1);
10059     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
10060     SwapEngines(1);         // and make that valid for second engine by swapping
10061     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10062     InitEngine(&second, 1);
10063     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10064     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10065     return 1;
10066 }
10067
10068 void
10069 NextMatchGame()
10070 {   // performs game initialization that does not invoke engines, and then tries to start the game
10071     int res, firstWhite, swapColors = 0;
10072     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10073     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10074     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10075     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10076     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10077     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10078     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10079     Reset(FALSE, first.pr != NoProc);
10080     res = LoadGameOrPosition(matchGame); // setup game
10081     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10082     if(!res) return; // abort when bad game/pos file
10083     TwoMachinesEvent();
10084 }
10085
10086 void UserAdjudicationEvent( int result )
10087 {
10088     ChessMove gameResult = GameIsDrawn;
10089
10090     if( result > 0 ) {
10091         gameResult = WhiteWins;
10092     }
10093     else if( result < 0 ) {
10094         gameResult = BlackWins;
10095     }
10096
10097     if( gameMode == TwoMachinesPlay ) {
10098         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10099     }
10100 }
10101
10102
10103 // [HGM] save: calculate checksum of game to make games easily identifiable
10104 int StringCheckSum(char *s)
10105 {
10106         int i = 0;
10107         if(s==NULL) return 0;
10108         while(*s) i = i*259 + *s++;
10109         return i;
10110 }
10111
10112 int GameCheckSum()
10113 {
10114         int i, sum=0;
10115         for(i=backwardMostMove; i<forwardMostMove; i++) {
10116                 sum += pvInfoList[i].depth;
10117                 sum += StringCheckSum(parseList[i]);
10118                 sum += StringCheckSum(commentList[i]);
10119                 sum *= 261;
10120         }
10121         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10122         return sum + StringCheckSum(commentList[i]);
10123 } // end of save patch
10124
10125 void
10126 GameEnds(result, resultDetails, whosays)
10127      ChessMove result;
10128      char *resultDetails;
10129      int whosays;
10130 {
10131     GameMode nextGameMode;
10132     int isIcsGame;
10133     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10134
10135     if(endingGame) return; /* [HGM] crash: forbid recursion */
10136     endingGame = 1;
10137     if(twoBoards) { // [HGM] dual: switch back to one board
10138         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10139         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10140     }
10141     if (appData.debugMode) {
10142       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10143               result, resultDetails ? resultDetails : "(null)", whosays);
10144     }
10145
10146     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10147
10148     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10149         /* If we are playing on ICS, the server decides when the
10150            game is over, but the engine can offer to draw, claim
10151            a draw, or resign.
10152          */
10153 #if ZIPPY
10154         if (appData.zippyPlay && first.initDone) {
10155             if (result == GameIsDrawn) {
10156                 /* In case draw still needs to be claimed */
10157                 SendToICS(ics_prefix);
10158                 SendToICS("draw\n");
10159             } else if (StrCaseStr(resultDetails, "resign")) {
10160                 SendToICS(ics_prefix);
10161                 SendToICS("resign\n");
10162             }
10163         }
10164 #endif
10165         endingGame = 0; /* [HGM] crash */
10166         return;
10167     }
10168
10169     /* If we're loading the game from a file, stop */
10170     if (whosays == GE_FILE) {
10171       (void) StopLoadGameTimer();
10172       gameFileFP = NULL;
10173     }
10174
10175     /* Cancel draw offers */
10176     first.offeredDraw = second.offeredDraw = 0;
10177
10178     /* If this is an ICS game, only ICS can really say it's done;
10179        if not, anyone can. */
10180     isIcsGame = (gameMode == IcsPlayingWhite ||
10181                  gameMode == IcsPlayingBlack ||
10182                  gameMode == IcsObserving    ||
10183                  gameMode == IcsExamining);
10184
10185     if (!isIcsGame || whosays == GE_ICS) {
10186         /* OK -- not an ICS game, or ICS said it was done */
10187         StopClocks();
10188         if (!isIcsGame && !appData.noChessProgram)
10189           SetUserThinkingEnables();
10190
10191         /* [HGM] if a machine claims the game end we verify this claim */
10192         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10193             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10194                 char claimer;
10195                 ChessMove trueResult = (ChessMove) -1;
10196
10197                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10198                                             first.twoMachinesColor[0] :
10199                                             second.twoMachinesColor[0] ;
10200
10201                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10202                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10203                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10204                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10205                 } else
10206                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10207                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10208                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10209                 } else
10210                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10211                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10212                 }
10213
10214                 // now verify win claims, but not in drop games, as we don't understand those yet
10215                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10216                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10217                     (result == WhiteWins && claimer == 'w' ||
10218                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10219                       if (appData.debugMode) {
10220                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10221                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10222                       }
10223                       if(result != trueResult) {
10224                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10225                               result = claimer == 'w' ? BlackWins : WhiteWins;
10226                               resultDetails = buf;
10227                       }
10228                 } else
10229                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10230                     && (forwardMostMove <= backwardMostMove ||
10231                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10232                         (claimer=='b')==(forwardMostMove&1))
10233                                                                                   ) {
10234                       /* [HGM] verify: draws that were not flagged are false claims */
10235                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10236                       result = claimer == 'w' ? BlackWins : WhiteWins;
10237                       resultDetails = buf;
10238                 }
10239                 /* (Claiming a loss is accepted no questions asked!) */
10240             }
10241             /* [HGM] bare: don't allow bare King to win */
10242             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10243                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10244                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10245                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10246                && result != GameIsDrawn)
10247             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10248                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10249                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10250                         if(p >= 0 && p <= (int)WhiteKing) k++;
10251                 }
10252                 if (appData.debugMode) {
10253                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10254                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10255                 }
10256                 if(k <= 1) {
10257                         result = GameIsDrawn;
10258                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10259                         resultDetails = buf;
10260                 }
10261             }
10262         }
10263
10264
10265         if(serverMoves != NULL && !loadFlag) { char c = '=';
10266             if(result==WhiteWins) c = '+';
10267             if(result==BlackWins) c = '-';
10268             if(resultDetails != NULL)
10269                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10270         }
10271         if (resultDetails != NULL) {
10272             gameInfo.result = result;
10273             gameInfo.resultDetails = StrSave(resultDetails);
10274
10275             /* display last move only if game was not loaded from file */
10276             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10277                 DisplayMove(currentMove - 1);
10278
10279             if (forwardMostMove != 0) {
10280                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10281                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10282                                                                 ) {
10283                     if (*appData.saveGameFile != NULLCHAR) {
10284                         SaveGameToFile(appData.saveGameFile, TRUE);
10285                     } else if (appData.autoSaveGames) {
10286                         AutoSaveGame();
10287                     }
10288                     if (*appData.savePositionFile != NULLCHAR) {
10289                         SavePositionToFile(appData.savePositionFile);
10290                     }
10291                 }
10292             }
10293
10294             /* Tell program how game ended in case it is learning */
10295             /* [HGM] Moved this to after saving the PGN, just in case */
10296             /* engine died and we got here through time loss. In that */
10297             /* case we will get a fatal error writing the pipe, which */
10298             /* would otherwise lose us the PGN.                       */
10299             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10300             /* output during GameEnds should never be fatal anymore   */
10301             if (gameMode == MachinePlaysWhite ||
10302                 gameMode == MachinePlaysBlack ||
10303                 gameMode == TwoMachinesPlay ||
10304                 gameMode == IcsPlayingWhite ||
10305                 gameMode == IcsPlayingBlack ||
10306                 gameMode == BeginningOfGame) {
10307                 char buf[MSG_SIZ];
10308                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10309                         resultDetails);
10310                 if (first.pr != NoProc) {
10311                     SendToProgram(buf, &first);
10312                 }
10313                 if (second.pr != NoProc &&
10314                     gameMode == TwoMachinesPlay) {
10315                     SendToProgram(buf, &second);
10316                 }
10317             }
10318         }
10319
10320         if (appData.icsActive) {
10321             if (appData.quietPlay &&
10322                 (gameMode == IcsPlayingWhite ||
10323                  gameMode == IcsPlayingBlack)) {
10324                 SendToICS(ics_prefix);
10325                 SendToICS("set shout 1\n");
10326             }
10327             nextGameMode = IcsIdle;
10328             ics_user_moved = FALSE;
10329             /* clean up premove.  It's ugly when the game has ended and the
10330              * premove highlights are still on the board.
10331              */
10332             if (gotPremove) {
10333               gotPremove = FALSE;
10334               ClearPremoveHighlights();
10335               DrawPosition(FALSE, boards[currentMove]);
10336             }
10337             if (whosays == GE_ICS) {
10338                 switch (result) {
10339                 case WhiteWins:
10340                     if (gameMode == IcsPlayingWhite)
10341                         PlayIcsWinSound();
10342                     else if(gameMode == IcsPlayingBlack)
10343                         PlayIcsLossSound();
10344                     break;
10345                 case BlackWins:
10346                     if (gameMode == IcsPlayingBlack)
10347                         PlayIcsWinSound();
10348                     else if(gameMode == IcsPlayingWhite)
10349                         PlayIcsLossSound();
10350                     break;
10351                 case GameIsDrawn:
10352                     PlayIcsDrawSound();
10353                     break;
10354                 default:
10355                     PlayIcsUnfinishedSound();
10356                 }
10357             }
10358         } else if (gameMode == EditGame ||
10359                    gameMode == PlayFromGameFile ||
10360                    gameMode == AnalyzeMode ||
10361                    gameMode == AnalyzeFile) {
10362             nextGameMode = gameMode;
10363         } else {
10364             nextGameMode = EndOfGame;
10365         }
10366         pausing = FALSE;
10367         ModeHighlight();
10368     } else {
10369         nextGameMode = gameMode;
10370     }
10371
10372     if (appData.noChessProgram) {
10373         gameMode = nextGameMode;
10374         ModeHighlight();
10375         endingGame = 0; /* [HGM] crash */
10376         return;
10377     }
10378
10379     if (first.reuse) {
10380         /* Put first chess program into idle state */
10381         if (first.pr != NoProc &&
10382             (gameMode == MachinePlaysWhite ||
10383              gameMode == MachinePlaysBlack ||
10384              gameMode == TwoMachinesPlay ||
10385              gameMode == IcsPlayingWhite ||
10386              gameMode == IcsPlayingBlack ||
10387              gameMode == BeginningOfGame)) {
10388             SendToProgram("force\n", &first);
10389             if (first.usePing) {
10390               char buf[MSG_SIZ];
10391               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10392               SendToProgram(buf, &first);
10393             }
10394         }
10395     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10396         /* Kill off first chess program */
10397         if (first.isr != NULL)
10398           RemoveInputSource(first.isr);
10399         first.isr = NULL;
10400
10401         if (first.pr != NoProc) {
10402             ExitAnalyzeMode();
10403             DoSleep( appData.delayBeforeQuit );
10404             SendToProgram("quit\n", &first);
10405             DoSleep( appData.delayAfterQuit );
10406             DestroyChildProcess(first.pr, first.useSigterm);
10407         }
10408         first.pr = NoProc;
10409     }
10410     if (second.reuse) {
10411         /* Put second chess program into idle state */
10412         if (second.pr != NoProc &&
10413             gameMode == TwoMachinesPlay) {
10414             SendToProgram("force\n", &second);
10415             if (second.usePing) {
10416               char buf[MSG_SIZ];
10417               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10418               SendToProgram(buf, &second);
10419             }
10420         }
10421     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10422         /* Kill off second chess program */
10423         if (second.isr != NULL)
10424           RemoveInputSource(second.isr);
10425         second.isr = NULL;
10426
10427         if (second.pr != NoProc) {
10428             DoSleep( appData.delayBeforeQuit );
10429             SendToProgram("quit\n", &second);
10430             DoSleep( appData.delayAfterQuit );
10431             DestroyChildProcess(second.pr, second.useSigterm);
10432         }
10433         second.pr = NoProc;
10434     }
10435
10436     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10437         char resChar = '=';
10438         switch (result) {
10439         case WhiteWins:
10440           resChar = '+';
10441           if (first.twoMachinesColor[0] == 'w') {
10442             first.matchWins++;
10443           } else {
10444             second.matchWins++;
10445           }
10446           break;
10447         case BlackWins:
10448           resChar = '-';
10449           if (first.twoMachinesColor[0] == 'b') {
10450             first.matchWins++;
10451           } else {
10452             second.matchWins++;
10453           }
10454           break;
10455         case GameUnfinished:
10456           resChar = ' ';
10457         default:
10458           break;
10459         }
10460
10461         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10462         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10463             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10464             ReserveGame(nextGame, resChar); // sets nextGame
10465             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10466             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10467         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10468
10469         if (nextGame <= appData.matchGames && !abortMatch) {
10470             gameMode = nextGameMode;
10471             matchGame = nextGame; // this will be overruled in tourney mode!
10472             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10473             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10474             endingGame = 0; /* [HGM] crash */
10475             return;
10476         } else {
10477             gameMode = nextGameMode;
10478             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10479                      first.tidy, second.tidy,
10480                      first.matchWins, second.matchWins,
10481                      appData.matchGames - (first.matchWins + second.matchWins));
10482             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10483             if(strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10484             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10485             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10486                 first.twoMachinesColor = "black\n";
10487                 second.twoMachinesColor = "white\n";
10488             } else {
10489                 first.twoMachinesColor = "white\n";
10490                 second.twoMachinesColor = "black\n";
10491             }
10492         }
10493     }
10494     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10495         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10496       ExitAnalyzeMode();
10497     gameMode = nextGameMode;
10498     ModeHighlight();
10499     endingGame = 0;  /* [HGM] crash */
10500     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10501         if(matchMode == TRUE) { // match through command line: exit with or without popup
10502             if(ranking) {
10503                 ToNrEvent(forwardMostMove);
10504                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10505                 else ExitEvent(0);
10506             } else DisplayFatalError(buf, 0, 0);
10507         } else { // match through menu; just stop, with or without popup
10508             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10509             ModeHighlight();
10510             if(ranking){
10511                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10512             } else DisplayNote(buf);
10513       }
10514       if(ranking) free(ranking);
10515     }
10516 }
10517
10518 /* Assumes program was just initialized (initString sent).
10519    Leaves program in force mode. */
10520 void
10521 FeedMovesToProgram(cps, upto)
10522      ChessProgramState *cps;
10523      int upto;
10524 {
10525     int i;
10526
10527     if (appData.debugMode)
10528       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10529               startedFromSetupPosition ? "position and " : "",
10530               backwardMostMove, upto, cps->which);
10531     if(currentlyInitializedVariant != gameInfo.variant) {
10532       char buf[MSG_SIZ];
10533         // [HGM] variantswitch: make engine aware of new variant
10534         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10535                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10536         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10537         SendToProgram(buf, cps);
10538         currentlyInitializedVariant = gameInfo.variant;
10539     }
10540     SendToProgram("force\n", cps);
10541     if (startedFromSetupPosition) {
10542         SendBoard(cps, backwardMostMove);
10543     if (appData.debugMode) {
10544         fprintf(debugFP, "feedMoves\n");
10545     }
10546     }
10547     for (i = backwardMostMove; i < upto; i++) {
10548         SendMoveToProgram(i, cps);
10549     }
10550 }
10551
10552
10553 int
10554 ResurrectChessProgram()
10555 {
10556      /* The chess program may have exited.
10557         If so, restart it and feed it all the moves made so far. */
10558     static int doInit = 0;
10559
10560     if (appData.noChessProgram) return 1;
10561
10562     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10563         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10564         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10565         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10566     } else {
10567         if (first.pr != NoProc) return 1;
10568         StartChessProgram(&first);
10569     }
10570     InitChessProgram(&first, FALSE);
10571     FeedMovesToProgram(&first, currentMove);
10572
10573     if (!first.sendTime) {
10574         /* can't tell gnuchess what its clock should read,
10575            so we bow to its notion. */
10576         ResetClocks();
10577         timeRemaining[0][currentMove] = whiteTimeRemaining;
10578         timeRemaining[1][currentMove] = blackTimeRemaining;
10579     }
10580
10581     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10582                 appData.icsEngineAnalyze) && first.analysisSupport) {
10583       SendToProgram("analyze\n", &first);
10584       first.analyzing = TRUE;
10585     }
10586     return 1;
10587 }
10588
10589 /*
10590  * Button procedures
10591  */
10592 void
10593 Reset(redraw, init)
10594      int redraw, init;
10595 {
10596     int i;
10597
10598     if (appData.debugMode) {
10599         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10600                 redraw, init, gameMode);
10601     }
10602     CleanupTail(); // [HGM] vari: delete any stored variations
10603     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10604     pausing = pauseExamInvalid = FALSE;
10605     startedFromSetupPosition = blackPlaysFirst = FALSE;
10606     firstMove = TRUE;
10607     whiteFlag = blackFlag = FALSE;
10608     userOfferedDraw = FALSE;
10609     hintRequested = bookRequested = FALSE;
10610     first.maybeThinking = FALSE;
10611     second.maybeThinking = FALSE;
10612     first.bookSuspend = FALSE; // [HGM] book
10613     second.bookSuspend = FALSE;
10614     thinkOutput[0] = NULLCHAR;
10615     lastHint[0] = NULLCHAR;
10616     ClearGameInfo(&gameInfo);
10617     gameInfo.variant = StringToVariant(appData.variant);
10618     ics_user_moved = ics_clock_paused = FALSE;
10619     ics_getting_history = H_FALSE;
10620     ics_gamenum = -1;
10621     white_holding[0] = black_holding[0] = NULLCHAR;
10622     ClearProgramStats();
10623     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10624
10625     ResetFrontEnd();
10626     ClearHighlights();
10627     flipView = appData.flipView;
10628     ClearPremoveHighlights();
10629     gotPremove = FALSE;
10630     alarmSounded = FALSE;
10631
10632     GameEnds(EndOfFile, NULL, GE_PLAYER);
10633     if(appData.serverMovesName != NULL) {
10634         /* [HGM] prepare to make moves file for broadcasting */
10635         clock_t t = clock();
10636         if(serverMoves != NULL) fclose(serverMoves);
10637         serverMoves = fopen(appData.serverMovesName, "r");
10638         if(serverMoves != NULL) {
10639             fclose(serverMoves);
10640             /* delay 15 sec before overwriting, so all clients can see end */
10641             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10642         }
10643         serverMoves = fopen(appData.serverMovesName, "w");
10644     }
10645
10646     ExitAnalyzeMode();
10647     gameMode = BeginningOfGame;
10648     ModeHighlight();
10649     if(appData.icsActive) gameInfo.variant = VariantNormal;
10650     currentMove = forwardMostMove = backwardMostMove = 0;
10651     InitPosition(redraw);
10652     for (i = 0; i < MAX_MOVES; i++) {
10653         if (commentList[i] != NULL) {
10654             free(commentList[i]);
10655             commentList[i] = NULL;
10656         }
10657     }
10658     ResetClocks();
10659     timeRemaining[0][0] = whiteTimeRemaining;
10660     timeRemaining[1][0] = blackTimeRemaining;
10661
10662     if (first.pr == NoProc) {
10663         StartChessProgram(&first);
10664     }
10665     if (init) {
10666             InitChessProgram(&first, startedFromSetupPosition);
10667     }
10668     DisplayTitle("");
10669     DisplayMessage("", "");
10670     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10671     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10672 }
10673
10674 void
10675 AutoPlayGameLoop()
10676 {
10677     for (;;) {
10678         if (!AutoPlayOneMove())
10679           return;
10680         if (matchMode || appData.timeDelay == 0)
10681           continue;
10682         if (appData.timeDelay < 0)
10683           return;
10684         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10685         break;
10686     }
10687 }
10688
10689
10690 int
10691 AutoPlayOneMove()
10692 {
10693     int fromX, fromY, toX, toY;
10694
10695     if (appData.debugMode) {
10696       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10697     }
10698
10699     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10700       return FALSE;
10701
10702     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10703       pvInfoList[currentMove].depth = programStats.depth;
10704       pvInfoList[currentMove].score = programStats.score;
10705       pvInfoList[currentMove].time  = 0;
10706       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10707     }
10708
10709     if (currentMove >= forwardMostMove) {
10710       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10711 //      gameMode = EndOfGame;
10712 //      ModeHighlight();
10713
10714       /* [AS] Clear current move marker at the end of a game */
10715       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10716
10717       return FALSE;
10718     }
10719
10720     toX = moveList[currentMove][2] - AAA;
10721     toY = moveList[currentMove][3] - ONE;
10722
10723     if (moveList[currentMove][1] == '@') {
10724         if (appData.highlightLastMove) {
10725             SetHighlights(-1, -1, toX, toY);
10726         }
10727     } else {
10728         fromX = moveList[currentMove][0] - AAA;
10729         fromY = moveList[currentMove][1] - ONE;
10730
10731         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10732
10733         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10734
10735         if (appData.highlightLastMove) {
10736             SetHighlights(fromX, fromY, toX, toY);
10737         }
10738     }
10739     DisplayMove(currentMove);
10740     SendMoveToProgram(currentMove++, &first);
10741     DisplayBothClocks();
10742     DrawPosition(FALSE, boards[currentMove]);
10743     // [HGM] PV info: always display, routine tests if empty
10744     DisplayComment(currentMove - 1, commentList[currentMove]);
10745     return TRUE;
10746 }
10747
10748
10749 int
10750 LoadGameOneMove(readAhead)
10751      ChessMove readAhead;
10752 {
10753     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10754     char promoChar = NULLCHAR;
10755     ChessMove moveType;
10756     char move[MSG_SIZ];
10757     char *p, *q;
10758
10759     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10760         gameMode != AnalyzeMode && gameMode != Training) {
10761         gameFileFP = NULL;
10762         return FALSE;
10763     }
10764
10765     yyboardindex = forwardMostMove;
10766     if (readAhead != EndOfFile) {
10767       moveType = readAhead;
10768     } else {
10769       if (gameFileFP == NULL)
10770           return FALSE;
10771       moveType = (ChessMove) Myylex();
10772     }
10773
10774     done = FALSE;
10775     switch (moveType) {
10776       case Comment:
10777         if (appData.debugMode)
10778           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10779         p = yy_text;
10780
10781         /* append the comment but don't display it */
10782         AppendComment(currentMove, p, FALSE);
10783         return TRUE;
10784
10785       case WhiteCapturesEnPassant:
10786       case BlackCapturesEnPassant:
10787       case WhitePromotion:
10788       case BlackPromotion:
10789       case WhiteNonPromotion:
10790       case BlackNonPromotion:
10791       case NormalMove:
10792       case WhiteKingSideCastle:
10793       case WhiteQueenSideCastle:
10794       case BlackKingSideCastle:
10795       case BlackQueenSideCastle:
10796       case WhiteKingSideCastleWild:
10797       case WhiteQueenSideCastleWild:
10798       case BlackKingSideCastleWild:
10799       case BlackQueenSideCastleWild:
10800       /* PUSH Fabien */
10801       case WhiteHSideCastleFR:
10802       case WhiteASideCastleFR:
10803       case BlackHSideCastleFR:
10804       case BlackASideCastleFR:
10805       /* POP Fabien */
10806         if (appData.debugMode)
10807           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10808         fromX = currentMoveString[0] - AAA;
10809         fromY = currentMoveString[1] - ONE;
10810         toX = currentMoveString[2] - AAA;
10811         toY = currentMoveString[3] - ONE;
10812         promoChar = currentMoveString[4];
10813         break;
10814
10815       case WhiteDrop:
10816       case BlackDrop:
10817         if (appData.debugMode)
10818           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10819         fromX = moveType == WhiteDrop ?
10820           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10821         (int) CharToPiece(ToLower(currentMoveString[0]));
10822         fromY = DROP_RANK;
10823         toX = currentMoveString[2] - AAA;
10824         toY = currentMoveString[3] - ONE;
10825         break;
10826
10827       case WhiteWins:
10828       case BlackWins:
10829       case GameIsDrawn:
10830       case GameUnfinished:
10831         if (appData.debugMode)
10832           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10833         p = strchr(yy_text, '{');
10834         if (p == NULL) p = strchr(yy_text, '(');
10835         if (p == NULL) {
10836             p = yy_text;
10837             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10838         } else {
10839             q = strchr(p, *p == '{' ? '}' : ')');
10840             if (q != NULL) *q = NULLCHAR;
10841             p++;
10842         }
10843         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10844         GameEnds(moveType, p, GE_FILE);
10845         done = TRUE;
10846         if (cmailMsgLoaded) {
10847             ClearHighlights();
10848             flipView = WhiteOnMove(currentMove);
10849             if (moveType == GameUnfinished) flipView = !flipView;
10850             if (appData.debugMode)
10851               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10852         }
10853         break;
10854
10855       case EndOfFile:
10856         if (appData.debugMode)
10857           fprintf(debugFP, "Parser hit end of file\n");
10858         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10859           case MT_NONE:
10860           case MT_CHECK:
10861             break;
10862           case MT_CHECKMATE:
10863           case MT_STAINMATE:
10864             if (WhiteOnMove(currentMove)) {
10865                 GameEnds(BlackWins, "Black mates", GE_FILE);
10866             } else {
10867                 GameEnds(WhiteWins, "White mates", GE_FILE);
10868             }
10869             break;
10870           case MT_STALEMATE:
10871             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10872             break;
10873         }
10874         done = TRUE;
10875         break;
10876
10877       case MoveNumberOne:
10878         if (lastLoadGameStart == GNUChessGame) {
10879             /* GNUChessGames have numbers, but they aren't move numbers */
10880             if (appData.debugMode)
10881               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10882                       yy_text, (int) moveType);
10883             return LoadGameOneMove(EndOfFile); /* tail recursion */
10884         }
10885         /* else fall thru */
10886
10887       case XBoardGame:
10888       case GNUChessGame:
10889       case PGNTag:
10890         /* Reached start of next game in file */
10891         if (appData.debugMode)
10892           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10893         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10894           case MT_NONE:
10895           case MT_CHECK:
10896             break;
10897           case MT_CHECKMATE:
10898           case MT_STAINMATE:
10899             if (WhiteOnMove(currentMove)) {
10900                 GameEnds(BlackWins, "Black mates", GE_FILE);
10901             } else {
10902                 GameEnds(WhiteWins, "White mates", GE_FILE);
10903             }
10904             break;
10905           case MT_STALEMATE:
10906             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10907             break;
10908         }
10909         done = TRUE;
10910         break;
10911
10912       case PositionDiagram:     /* should not happen; ignore */
10913       case ElapsedTime:         /* ignore */
10914       case NAG:                 /* ignore */
10915         if (appData.debugMode)
10916           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10917                   yy_text, (int) moveType);
10918         return LoadGameOneMove(EndOfFile); /* tail recursion */
10919
10920       case IllegalMove:
10921         if (appData.testLegality) {
10922             if (appData.debugMode)
10923               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10924             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10925                     (forwardMostMove / 2) + 1,
10926                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10927             DisplayError(move, 0);
10928             done = TRUE;
10929         } else {
10930             if (appData.debugMode)
10931               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10932                       yy_text, currentMoveString);
10933             fromX = currentMoveString[0] - AAA;
10934             fromY = currentMoveString[1] - ONE;
10935             toX = currentMoveString[2] - AAA;
10936             toY = currentMoveString[3] - ONE;
10937             promoChar = currentMoveString[4];
10938         }
10939         break;
10940
10941       case AmbiguousMove:
10942         if (appData.debugMode)
10943           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10944         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10945                 (forwardMostMove / 2) + 1,
10946                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10947         DisplayError(move, 0);
10948         done = TRUE;
10949         break;
10950
10951       default:
10952       case ImpossibleMove:
10953         if (appData.debugMode)
10954           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10955         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10956                 (forwardMostMove / 2) + 1,
10957                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10958         DisplayError(move, 0);
10959         done = TRUE;
10960         break;
10961     }
10962
10963     if (done) {
10964         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10965             DrawPosition(FALSE, boards[currentMove]);
10966             DisplayBothClocks();
10967             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10968               DisplayComment(currentMove - 1, commentList[currentMove]);
10969         }
10970         (void) StopLoadGameTimer();
10971         gameFileFP = NULL;
10972         cmailOldMove = forwardMostMove;
10973         return FALSE;
10974     } else {
10975         /* currentMoveString is set as a side-effect of yylex */
10976
10977         thinkOutput[0] = NULLCHAR;
10978         MakeMove(fromX, fromY, toX, toY, promoChar);
10979         currentMove = forwardMostMove;
10980         return TRUE;
10981     }
10982 }
10983
10984 /* Load the nth game from the given file */
10985 int
10986 LoadGameFromFile(filename, n, title, useList)
10987      char *filename;
10988      int n;
10989      char *title;
10990      /*Boolean*/ int useList;
10991 {
10992     FILE *f;
10993     char buf[MSG_SIZ];
10994
10995     if (strcmp(filename, "-") == 0) {
10996         f = stdin;
10997         title = "stdin";
10998     } else {
10999         f = fopen(filename, "rb");
11000         if (f == NULL) {
11001           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11002             DisplayError(buf, errno);
11003             return FALSE;
11004         }
11005     }
11006     if (fseek(f, 0, 0) == -1) {
11007         /* f is not seekable; probably a pipe */
11008         useList = FALSE;
11009     }
11010     if (useList && n == 0) {
11011         int error = GameListBuild(f);
11012         if (error) {
11013             DisplayError(_("Cannot build game list"), error);
11014         } else if (!ListEmpty(&gameList) &&
11015                    ((ListGame *) gameList.tailPred)->number > 1) {
11016             GameListPopUp(f, title);
11017             return TRUE;
11018         }
11019         GameListDestroy();
11020         n = 1;
11021     }
11022     if (n == 0) n = 1;
11023     return LoadGame(f, n, title, FALSE);
11024 }
11025
11026
11027 void
11028 MakeRegisteredMove()
11029 {
11030     int fromX, fromY, toX, toY;
11031     char promoChar;
11032     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11033         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11034           case CMAIL_MOVE:
11035           case CMAIL_DRAW:
11036             if (appData.debugMode)
11037               fprintf(debugFP, "Restoring %s for game %d\n",
11038                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11039
11040             thinkOutput[0] = NULLCHAR;
11041             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11042             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11043             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11044             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11045             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11046             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11047             MakeMove(fromX, fromY, toX, toY, promoChar);
11048             ShowMove(fromX, fromY, toX, toY);
11049
11050             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11051               case MT_NONE:
11052               case MT_CHECK:
11053                 break;
11054
11055               case MT_CHECKMATE:
11056               case MT_STAINMATE:
11057                 if (WhiteOnMove(currentMove)) {
11058                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11059                 } else {
11060                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11061                 }
11062                 break;
11063
11064               case MT_STALEMATE:
11065                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11066                 break;
11067             }
11068
11069             break;
11070
11071           case CMAIL_RESIGN:
11072             if (WhiteOnMove(currentMove)) {
11073                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11074             } else {
11075                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11076             }
11077             break;
11078
11079           case CMAIL_ACCEPT:
11080             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11081             break;
11082
11083           default:
11084             break;
11085         }
11086     }
11087
11088     return;
11089 }
11090
11091 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11092 int
11093 CmailLoadGame(f, gameNumber, title, useList)
11094      FILE *f;
11095      int gameNumber;
11096      char *title;
11097      int useList;
11098 {
11099     int retVal;
11100
11101     if (gameNumber > nCmailGames) {
11102         DisplayError(_("No more games in this message"), 0);
11103         return FALSE;
11104     }
11105     if (f == lastLoadGameFP) {
11106         int offset = gameNumber - lastLoadGameNumber;
11107         if (offset == 0) {
11108             cmailMsg[0] = NULLCHAR;
11109             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11110                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11111                 nCmailMovesRegistered--;
11112             }
11113             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11114             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11115                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11116             }
11117         } else {
11118             if (! RegisterMove()) return FALSE;
11119         }
11120     }
11121
11122     retVal = LoadGame(f, gameNumber, title, useList);
11123
11124     /* Make move registered during previous look at this game, if any */
11125     MakeRegisteredMove();
11126
11127     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11128         commentList[currentMove]
11129           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11130         DisplayComment(currentMove - 1, commentList[currentMove]);
11131     }
11132
11133     return retVal;
11134 }
11135
11136 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11137 int
11138 ReloadGame(offset)
11139      int offset;
11140 {
11141     int gameNumber = lastLoadGameNumber + offset;
11142     if (lastLoadGameFP == NULL) {
11143         DisplayError(_("No game has been loaded yet"), 0);
11144         return FALSE;
11145     }
11146     if (gameNumber <= 0) {
11147         DisplayError(_("Can't back up any further"), 0);
11148         return FALSE;
11149     }
11150     if (cmailMsgLoaded) {
11151         return CmailLoadGame(lastLoadGameFP, gameNumber,
11152                              lastLoadGameTitle, lastLoadGameUseList);
11153     } else {
11154         return LoadGame(lastLoadGameFP, gameNumber,
11155                         lastLoadGameTitle, lastLoadGameUseList);
11156     }
11157 }
11158
11159 int keys[EmptySquare+1];
11160
11161 int
11162 PositionMatches(Board b1, Board b2)
11163 {
11164     int r, f, sum=0;
11165     switch(appData.searchMode) {
11166         case 1: return CompareWithRights(b1, b2);
11167         case 2:
11168             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11169                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11170             }
11171             return TRUE;
11172         case 3:
11173             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11174               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11175                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11176             }
11177             return sum==0;
11178         case 4:
11179             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11180                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11181             }
11182             return sum==0;
11183     }
11184     return TRUE;
11185 }
11186
11187 #define Q_PROMO  4
11188 #define Q_EP     3
11189 #define Q_BCASTL 2
11190 #define Q_WCASTL 1
11191
11192 int pieceList[256], quickBoard[256];
11193 ChessSquare pieceType[256] = { EmptySquare };
11194 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11195 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11196 int soughtTotal, turn;
11197 Boolean epOK, flipSearch;
11198
11199 typedef struct {
11200     unsigned char piece, to;
11201 } Move;
11202
11203 #define DSIZE (250000)
11204
11205 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11206 Move *moveDatabase = initialSpace;
11207 unsigned int movePtr, dataSize = DSIZE;
11208
11209 int MakePieceList(Board board, int *counts)
11210 {
11211     int r, f, n=Q_PROMO, total=0;
11212     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11213     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11214         int sq = f + (r<<4);
11215         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11216             quickBoard[sq] = ++n;
11217             pieceList[n] = sq;
11218             pieceType[n] = board[r][f];
11219             counts[board[r][f]]++;
11220             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11221             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11222             total++;
11223         }
11224     }
11225     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11226     return total;
11227 }
11228
11229 void PackMove(int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11230 {
11231     int sq = fromX + (fromY<<4);
11232     int piece = quickBoard[sq];
11233     quickBoard[sq] = 0;
11234     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11235     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11236         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11237         moveDatabase[movePtr++].piece = Q_WCASTL;
11238         quickBoard[sq] = piece;
11239         piece = quickBoard[from]; quickBoard[from] = 0;
11240         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11241     } else
11242     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11243         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11244         moveDatabase[movePtr++].piece = Q_BCASTL;
11245         quickBoard[sq] = piece;
11246         piece = quickBoard[from]; quickBoard[from] = 0;
11247         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11248     } else
11249     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11250         quickBoard[(fromY<<4)+toX] = 0;
11251         moveDatabase[movePtr].piece = Q_EP;
11252         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11253         moveDatabase[movePtr].to = sq;
11254     } else
11255     if(promoPiece != pieceType[piece]) {
11256         moveDatabase[movePtr++].piece = Q_PROMO;
11257         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11258     }
11259     moveDatabase[movePtr].piece = piece;
11260     quickBoard[sq] = piece;
11261     movePtr++;
11262 }
11263
11264 int PackGame(Board board)
11265 {
11266     Move *newSpace = NULL;
11267     moveDatabase[movePtr].piece = 0; // terminate previous game
11268     if(movePtr > dataSize) {
11269         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11270         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11271         if(dataSize) newSpace = (Move*) calloc(8*dataSize + 1000, sizeof(Move));
11272         if(newSpace) {
11273             int i;
11274             Move *p = moveDatabase, *q = newSpace;
11275             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11276             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11277             moveDatabase = newSpace;
11278         } else { // calloc failed, we must be out of memory. Too bad...
11279             dataSize = 0; // prevent calloc events for all subsequent games
11280             return 0;     // and signal this one isn't cached
11281         }
11282     }
11283     movePtr++;
11284     MakePieceList(board, counts);
11285     return movePtr;
11286 }
11287
11288 int QuickCompare(Board board, int *minCounts, int *maxCounts)
11289 {   // compare according to search mode
11290     int r, f;
11291     switch(appData.searchMode)
11292     {
11293       case 1: // exact position match
11294         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11295         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11296             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11297         }
11298         break;
11299       case 2: // can have extra material on empty squares
11300         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11301             if(board[r][f] == EmptySquare) continue;
11302             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11303         }
11304         break;
11305       case 3: // material with exact Pawn structure
11306         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11307             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11308             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11309         } // fall through to material comparison
11310       case 4: // exact material
11311         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11312         break;
11313       case 6: // material range with given imbalance
11314         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11315         // fall through to range comparison
11316       case 5: // material range
11317         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11318     }
11319     return TRUE;
11320 }
11321
11322 int QuickScan(Board board, Move *move)
11323 {   // reconstruct game,and compare all positions in it
11324     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11325     do {
11326         int piece = move->piece;
11327         int to = move->to, from = pieceList[piece];
11328         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11329           if(!piece) return -1;
11330           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11331             piece = (++move)->piece;
11332             from = pieceList[piece];
11333             counts[pieceType[piece]]--;
11334             pieceType[piece] = (ChessSquare) move->to;
11335             counts[move->to]++;
11336           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11337             counts[pieceType[quickBoard[to]]]--;
11338             quickBoard[to] = 0; total--;
11339             move++;
11340             continue;
11341           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11342             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11343             from  = pieceList[piece]; // so this must be King
11344             quickBoard[from] = 0;
11345             quickBoard[to] = piece;
11346             pieceList[piece] = to;
11347             move++;
11348             continue;
11349           }
11350         }
11351         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11352         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11353         quickBoard[from] = 0;
11354         quickBoard[to] = piece;
11355         pieceList[piece] = to;
11356         cnt++; turn ^= 3;
11357         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11358            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11359            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11360                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11361           ) {
11362             static int lastCounts[EmptySquare+1];
11363             int i;
11364             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11365             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11366         } else stretch = 0;
11367         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11368         move++;
11369     } while(1);
11370 }
11371
11372 void InitSearch()
11373 {
11374     int r, f;
11375     flipSearch = FALSE;
11376     CopyBoard(soughtBoard, boards[currentMove]);
11377     soughtTotal = MakePieceList(soughtBoard, maxSought);
11378     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11379     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11380     CopyBoard(reverseBoard, boards[currentMove]);
11381     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11382         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11383         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11384         reverseBoard[r][f] = piece;
11385     }
11386     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11387     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11388     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11389                  || (boards[currentMove][CASTLING][2] == NoRights || 
11390                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11391                  && (boards[currentMove][CASTLING][5] == NoRights || 
11392                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11393       ) {
11394         flipSearch = TRUE;
11395         CopyBoard(flipBoard, soughtBoard);
11396         CopyBoard(rotateBoard, reverseBoard);
11397         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11398             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11399             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11400         }
11401     }
11402     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11403     if(appData.searchMode >= 5) {
11404         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11405         MakePieceList(soughtBoard, minSought);
11406         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11407     }
11408     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11409         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11410 }
11411
11412 GameInfo dummyInfo;
11413
11414 int GameContainsPosition(FILE *f, ListGame *lg)
11415 {
11416     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11417     int fromX, fromY, toX, toY;
11418     char promoChar;
11419     static int initDone=FALSE;
11420
11421     // weed out games based on numerical tag comparison
11422     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11423     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11424     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11425     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11426     if(!initDone) {
11427         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11428         initDone = TRUE;
11429     }
11430     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11431     else CopyBoard(boards[scratch], initialPosition); // default start position
11432     if(lg->moves) {
11433         turn = btm + 1;
11434         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11435         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11436     }
11437     if(btm) plyNr++;
11438     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11439     fseek(f, lg->offset, 0);
11440     yynewfile(f);
11441     while(1) {
11442         yyboardindex = scratch;
11443         quickFlag = plyNr+1;
11444         next = Myylex();
11445         quickFlag = 0;
11446         switch(next) {
11447             case PGNTag:
11448                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11449             default:
11450                 continue;
11451
11452             case XBoardGame:
11453             case GNUChessGame:
11454                 if(plyNr) return -1; // after we have seen moves, this is for new game
11455               continue;
11456
11457             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11458             case ImpossibleMove:
11459             case WhiteWins: // game ends here with these four
11460             case BlackWins:
11461             case GameIsDrawn:
11462             case GameUnfinished:
11463                 return -1;
11464
11465             case IllegalMove:
11466                 if(appData.testLegality) return -1;
11467             case WhiteCapturesEnPassant:
11468             case BlackCapturesEnPassant:
11469             case WhitePromotion:
11470             case BlackPromotion:
11471             case WhiteNonPromotion:
11472             case BlackNonPromotion:
11473             case NormalMove:
11474             case WhiteKingSideCastle:
11475             case WhiteQueenSideCastle:
11476             case BlackKingSideCastle:
11477             case BlackQueenSideCastle:
11478             case WhiteKingSideCastleWild:
11479             case WhiteQueenSideCastleWild:
11480             case BlackKingSideCastleWild:
11481             case BlackQueenSideCastleWild:
11482             case WhiteHSideCastleFR:
11483             case WhiteASideCastleFR:
11484             case BlackHSideCastleFR:
11485             case BlackASideCastleFR:
11486                 fromX = currentMoveString[0] - AAA;
11487                 fromY = currentMoveString[1] - ONE;
11488                 toX = currentMoveString[2] - AAA;
11489                 toY = currentMoveString[3] - ONE;
11490                 promoChar = currentMoveString[4];
11491                 break;
11492             case WhiteDrop:
11493             case BlackDrop:
11494                 fromX = next == WhiteDrop ?
11495                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11496                   (int) CharToPiece(ToLower(currentMoveString[0]));
11497                 fromY = DROP_RANK;
11498                 toX = currentMoveString[2] - AAA;
11499                 toY = currentMoveString[3] - ONE;
11500                 promoChar = 0;
11501                 break;
11502         }
11503         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11504         plyNr++;
11505         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11506         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11507         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11508         if(appData.findMirror) {
11509             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11510             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11511         }
11512     }
11513 }
11514
11515 /* Load the nth game from open file f */
11516 int
11517 LoadGame(f, gameNumber, title, useList)
11518      FILE *f;
11519      int gameNumber;
11520      char *title;
11521      int useList;
11522 {
11523     ChessMove cm;
11524     char buf[MSG_SIZ];
11525     int gn = gameNumber;
11526     ListGame *lg = NULL;
11527     int numPGNTags = 0;
11528     int err, pos = -1;
11529     GameMode oldGameMode;
11530     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11531
11532     if (appData.debugMode)
11533         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11534
11535     if (gameMode == Training )
11536         SetTrainingModeOff();
11537
11538     oldGameMode = gameMode;
11539     if (gameMode != BeginningOfGame) {
11540       Reset(FALSE, TRUE);
11541     }
11542
11543     gameFileFP = f;
11544     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11545         fclose(lastLoadGameFP);
11546     }
11547
11548     if (useList) {
11549         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11550
11551         if (lg) {
11552             fseek(f, lg->offset, 0);
11553             GameListHighlight(gameNumber);
11554             pos = lg->position;
11555             gn = 1;
11556         }
11557         else {
11558             DisplayError(_("Game number out of range"), 0);
11559             return FALSE;
11560         }
11561     } else {
11562         GameListDestroy();
11563         if (fseek(f, 0, 0) == -1) {
11564             if (f == lastLoadGameFP ?
11565                 gameNumber == lastLoadGameNumber + 1 :
11566                 gameNumber == 1) {
11567                 gn = 1;
11568             } else {
11569                 DisplayError(_("Can't seek on game file"), 0);
11570                 return FALSE;
11571             }
11572         }
11573     }
11574     lastLoadGameFP = f;
11575     lastLoadGameNumber = gameNumber;
11576     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11577     lastLoadGameUseList = useList;
11578
11579     yynewfile(f);
11580
11581     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11582       snprintf(buf, sizeof(buf), _("%s vs. %s"), lg->gameInfo.white,
11583                 lg->gameInfo.black);
11584             DisplayTitle(buf);
11585     } else if (*title != NULLCHAR) {
11586         if (gameNumber > 1) {
11587           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11588             DisplayTitle(buf);
11589         } else {
11590             DisplayTitle(title);
11591         }
11592     }
11593
11594     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11595         gameMode = PlayFromGameFile;
11596         ModeHighlight();
11597     }
11598
11599     currentMove = forwardMostMove = backwardMostMove = 0;
11600     CopyBoard(boards[0], initialPosition);
11601     StopClocks();
11602
11603     /*
11604      * Skip the first gn-1 games in the file.
11605      * Also skip over anything that precedes an identifiable
11606      * start of game marker, to avoid being confused by
11607      * garbage at the start of the file.  Currently
11608      * recognized start of game markers are the move number "1",
11609      * the pattern "gnuchess .* game", the pattern
11610      * "^[#;%] [^ ]* game file", and a PGN tag block.
11611      * A game that starts with one of the latter two patterns
11612      * will also have a move number 1, possibly
11613      * following a position diagram.
11614      * 5-4-02: Let's try being more lenient and allowing a game to
11615      * start with an unnumbered move.  Does that break anything?
11616      */
11617     cm = lastLoadGameStart = EndOfFile;
11618     while (gn > 0) {
11619         yyboardindex = forwardMostMove;
11620         cm = (ChessMove) Myylex();
11621         switch (cm) {
11622           case EndOfFile:
11623             if (cmailMsgLoaded) {
11624                 nCmailGames = CMAIL_MAX_GAMES - gn;
11625             } else {
11626                 Reset(TRUE, TRUE);
11627                 DisplayError(_("Game not found in file"), 0);
11628             }
11629             return FALSE;
11630
11631           case GNUChessGame:
11632           case XBoardGame:
11633             gn--;
11634             lastLoadGameStart = cm;
11635             break;
11636
11637           case MoveNumberOne:
11638             switch (lastLoadGameStart) {
11639               case GNUChessGame:
11640               case XBoardGame:
11641               case PGNTag:
11642                 break;
11643               case MoveNumberOne:
11644               case EndOfFile:
11645                 gn--;           /* count this game */
11646                 lastLoadGameStart = cm;
11647                 break;
11648               default:
11649                 /* impossible */
11650                 break;
11651             }
11652             break;
11653
11654           case PGNTag:
11655             switch (lastLoadGameStart) {
11656               case GNUChessGame:
11657               case PGNTag:
11658               case MoveNumberOne:
11659               case EndOfFile:
11660                 gn--;           /* count this game */
11661                 lastLoadGameStart = cm;
11662                 break;
11663               case XBoardGame:
11664                 lastLoadGameStart = cm; /* game counted already */
11665                 break;
11666               default:
11667                 /* impossible */
11668                 break;
11669             }
11670             if (gn > 0) {
11671                 do {
11672                     yyboardindex = forwardMostMove;
11673                     cm = (ChessMove) Myylex();
11674                 } while (cm == PGNTag || cm == Comment);
11675             }
11676             break;
11677
11678           case WhiteWins:
11679           case BlackWins:
11680           case GameIsDrawn:
11681             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11682                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11683                     != CMAIL_OLD_RESULT) {
11684                     nCmailResults ++ ;
11685                     cmailResult[  CMAIL_MAX_GAMES
11686                                 - gn - 1] = CMAIL_OLD_RESULT;
11687                 }
11688             }
11689             break;
11690
11691           case NormalMove:
11692             /* Only a NormalMove can be at the start of a game
11693              * without a position diagram. */
11694             if (lastLoadGameStart == EndOfFile ) {
11695               gn--;
11696               lastLoadGameStart = MoveNumberOne;
11697             }
11698             break;
11699
11700           default:
11701             break;
11702         }
11703     }
11704
11705     if (appData.debugMode)
11706       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11707
11708     if (cm == XBoardGame) {
11709         /* Skip any header junk before position diagram and/or move 1 */
11710         for (;;) {
11711             yyboardindex = forwardMostMove;
11712             cm = (ChessMove) Myylex();
11713
11714             if (cm == EndOfFile ||
11715                 cm == GNUChessGame || cm == XBoardGame) {
11716                 /* Empty game; pretend end-of-file and handle later */
11717                 cm = EndOfFile;
11718                 break;
11719             }
11720
11721             if (cm == MoveNumberOne || cm == PositionDiagram ||
11722                 cm == PGNTag || cm == Comment)
11723               break;
11724         }
11725     } else if (cm == GNUChessGame) {
11726         if (gameInfo.event != NULL) {
11727             free(gameInfo.event);
11728         }
11729         gameInfo.event = StrSave(yy_text);
11730     }
11731
11732     startedFromSetupPosition = FALSE;
11733     while (cm == PGNTag) {
11734         if (appData.debugMode)
11735           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11736         err = ParsePGNTag(yy_text, &gameInfo);
11737         if (!err) numPGNTags++;
11738
11739         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11740         if(gameInfo.variant != oldVariant) {
11741             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11742             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11743             InitPosition(TRUE);
11744             oldVariant = gameInfo.variant;
11745             if (appData.debugMode)
11746               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11747         }
11748
11749
11750         if (gameInfo.fen != NULL) {
11751           Board initial_position;
11752           startedFromSetupPosition = TRUE;
11753           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11754             Reset(TRUE, TRUE);
11755             DisplayError(_("Bad FEN position in file"), 0);
11756             return FALSE;
11757           }
11758           CopyBoard(boards[0], initial_position);
11759           if (blackPlaysFirst) {
11760             currentMove = forwardMostMove = backwardMostMove = 1;
11761             CopyBoard(boards[1], initial_position);
11762             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11763             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11764             timeRemaining[0][1] = whiteTimeRemaining;
11765             timeRemaining[1][1] = blackTimeRemaining;
11766             if (commentList[0] != NULL) {
11767               commentList[1] = commentList[0];
11768               commentList[0] = NULL;
11769             }
11770           } else {
11771             currentMove = forwardMostMove = backwardMostMove = 0;
11772           }
11773           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11774           {   int i;
11775               initialRulePlies = FENrulePlies;
11776               for( i=0; i< nrCastlingRights; i++ )
11777                   initialRights[i] = initial_position[CASTLING][i];
11778           }
11779           yyboardindex = forwardMostMove;
11780           free(gameInfo.fen);
11781           gameInfo.fen = NULL;
11782         }
11783
11784         yyboardindex = forwardMostMove;
11785         cm = (ChessMove) Myylex();
11786
11787         /* Handle comments interspersed among the tags */
11788         while (cm == Comment) {
11789             char *p;
11790             if (appData.debugMode)
11791               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11792             p = yy_text;
11793             AppendComment(currentMove, p, FALSE);
11794             yyboardindex = forwardMostMove;
11795             cm = (ChessMove) Myylex();
11796         }
11797     }
11798
11799     /* don't rely on existence of Event tag since if game was
11800      * pasted from clipboard the Event tag may not exist
11801      */
11802     if (numPGNTags > 0){
11803         char *tags;
11804         if (gameInfo.variant == VariantNormal) {
11805           VariantClass v = StringToVariant(gameInfo.event);
11806           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11807           if(v < VariantShogi) gameInfo.variant = v;
11808         }
11809         if (!matchMode) {
11810           if( appData.autoDisplayTags ) {
11811             tags = PGNTags(&gameInfo);
11812             TagsPopUp(tags, CmailMsg());
11813             free(tags);
11814           }
11815         }
11816     } else {
11817         /* Make something up, but don't display it now */
11818         SetGameInfo();
11819         TagsPopDown();
11820     }
11821
11822     if (cm == PositionDiagram) {
11823         int i, j;
11824         char *p;
11825         Board initial_position;
11826
11827         if (appData.debugMode)
11828           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11829
11830         if (!startedFromSetupPosition) {
11831             p = yy_text;
11832             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11833               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11834                 switch (*p) {
11835                   case '{':
11836                   case '[':
11837                   case '-':
11838                   case ' ':
11839                   case '\t':
11840                   case '\n':
11841                   case '\r':
11842                     break;
11843                   default:
11844                     initial_position[i][j++] = CharToPiece(*p);
11845                     break;
11846                 }
11847             while (*p == ' ' || *p == '\t' ||
11848                    *p == '\n' || *p == '\r') p++;
11849
11850             if (strncmp(p, "black", strlen("black"))==0)
11851               blackPlaysFirst = TRUE;
11852             else
11853               blackPlaysFirst = FALSE;
11854             startedFromSetupPosition = TRUE;
11855
11856             CopyBoard(boards[0], initial_position);
11857             if (blackPlaysFirst) {
11858                 currentMove = forwardMostMove = backwardMostMove = 1;
11859                 CopyBoard(boards[1], initial_position);
11860                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11861                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11862                 timeRemaining[0][1] = whiteTimeRemaining;
11863                 timeRemaining[1][1] = blackTimeRemaining;
11864                 if (commentList[0] != NULL) {
11865                     commentList[1] = commentList[0];
11866                     commentList[0] = NULL;
11867                 }
11868             } else {
11869                 currentMove = forwardMostMove = backwardMostMove = 0;
11870             }
11871         }
11872         yyboardindex = forwardMostMove;
11873         cm = (ChessMove) Myylex();
11874     }
11875
11876     if (first.pr == NoProc) {
11877         StartChessProgram(&first);
11878     }
11879     InitChessProgram(&first, FALSE);
11880     SendToProgram("force\n", &first);
11881     if (startedFromSetupPosition) {
11882         SendBoard(&first, forwardMostMove);
11883     if (appData.debugMode) {
11884         fprintf(debugFP, "Load Game\n");
11885     }
11886         DisplayBothClocks();
11887     }
11888
11889     /* [HGM] server: flag to write setup moves in broadcast file as one */
11890     loadFlag = appData.suppressLoadMoves;
11891
11892     while (cm == Comment) {
11893         char *p;
11894         if (appData.debugMode)
11895           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11896         p = yy_text;
11897         AppendComment(currentMove, p, FALSE);
11898         yyboardindex = forwardMostMove;
11899         cm = (ChessMove) Myylex();
11900     }
11901
11902     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11903         cm == WhiteWins || cm == BlackWins ||
11904         cm == GameIsDrawn || cm == GameUnfinished) {
11905         DisplayMessage("", _("No moves in game"));
11906         if (cmailMsgLoaded) {
11907             if (appData.debugMode)
11908               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11909             ClearHighlights();
11910             flipView = FALSE;
11911         }
11912         DrawPosition(FALSE, boards[currentMove]);
11913         DisplayBothClocks();
11914         gameMode = EditGame;
11915         ModeHighlight();
11916         gameFileFP = NULL;
11917         cmailOldMove = 0;
11918         return TRUE;
11919     }
11920
11921     // [HGM] PV info: routine tests if comment empty
11922     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11923         DisplayComment(currentMove - 1, commentList[currentMove]);
11924     }
11925     if (!matchMode && appData.timeDelay != 0)
11926       DrawPosition(FALSE, boards[currentMove]);
11927
11928     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11929       programStats.ok_to_send = 1;
11930     }
11931
11932     /* if the first token after the PGN tags is a move
11933      * and not move number 1, retrieve it from the parser
11934      */
11935     if (cm != MoveNumberOne)
11936         LoadGameOneMove(cm);
11937
11938     /* load the remaining moves from the file */
11939     while (LoadGameOneMove(EndOfFile)) {
11940       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11941       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11942     }
11943
11944     /* rewind to the start of the game */
11945     currentMove = backwardMostMove;
11946
11947     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11948
11949     if (oldGameMode == AnalyzeFile ||
11950         oldGameMode == AnalyzeMode) {
11951       AnalyzeFileEvent();
11952     }
11953
11954     if (!matchMode && pos >= 0) {
11955         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11956     } else
11957     if (matchMode || appData.timeDelay == 0) {
11958       ToEndEvent();
11959     } else if (appData.timeDelay > 0) {
11960       AutoPlayGameLoop();
11961     }
11962
11963     if (appData.debugMode)
11964         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11965
11966     loadFlag = 0; /* [HGM] true game starts */
11967     return TRUE;
11968 }
11969
11970 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11971 int
11972 ReloadPosition(offset)
11973      int offset;
11974 {
11975     int positionNumber = lastLoadPositionNumber + offset;
11976     if (lastLoadPositionFP == NULL) {
11977         DisplayError(_("No position has been loaded yet"), 0);
11978         return FALSE;
11979     }
11980     if (positionNumber <= 0) {
11981         DisplayError(_("Can't back up any further"), 0);
11982         return FALSE;
11983     }
11984     return LoadPosition(lastLoadPositionFP, positionNumber,
11985                         lastLoadPositionTitle);
11986 }
11987
11988 /* Load the nth position from the given file */
11989 int
11990 LoadPositionFromFile(filename, n, title)
11991      char *filename;
11992      int n;
11993      char *title;
11994 {
11995     FILE *f;
11996     char buf[MSG_SIZ];
11997
11998     if (strcmp(filename, "-") == 0) {
11999         return LoadPosition(stdin, n, "stdin");
12000     } else {
12001         f = fopen(filename, "rb");
12002         if (f == NULL) {
12003             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12004             DisplayError(buf, errno);
12005             return FALSE;
12006         } else {
12007             return LoadPosition(f, n, title);
12008         }
12009     }
12010 }
12011
12012 /* Load the nth position from the given open file, and close it */
12013 int
12014 LoadPosition(f, positionNumber, title)
12015      FILE *f;
12016      int positionNumber;
12017      char *title;
12018 {
12019     char *p, line[MSG_SIZ];
12020     Board initial_position;
12021     int i, j, fenMode, pn;
12022
12023     if (gameMode == Training )
12024         SetTrainingModeOff();
12025
12026     if (gameMode != BeginningOfGame) {
12027         Reset(FALSE, TRUE);
12028     }
12029     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12030         fclose(lastLoadPositionFP);
12031     }
12032     if (positionNumber == 0) positionNumber = 1;
12033     lastLoadPositionFP = f;
12034     lastLoadPositionNumber = positionNumber;
12035     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12036     if (first.pr == NoProc && !appData.noChessProgram) {
12037       StartChessProgram(&first);
12038       InitChessProgram(&first, FALSE);
12039     }
12040     pn = positionNumber;
12041     if (positionNumber < 0) {
12042         /* Negative position number means to seek to that byte offset */
12043         if (fseek(f, -positionNumber, 0) == -1) {
12044             DisplayError(_("Can't seek on position file"), 0);
12045             return FALSE;
12046         };
12047         pn = 1;
12048     } else {
12049         if (fseek(f, 0, 0) == -1) {
12050             if (f == lastLoadPositionFP ?
12051                 positionNumber == lastLoadPositionNumber + 1 :
12052                 positionNumber == 1) {
12053                 pn = 1;
12054             } else {
12055                 DisplayError(_("Can't seek on position file"), 0);
12056                 return FALSE;
12057             }
12058         }
12059     }
12060     /* See if this file is FEN or old-style xboard */
12061     if (fgets(line, MSG_SIZ, f) == NULL) {
12062         DisplayError(_("Position not found in file"), 0);
12063         return FALSE;
12064     }
12065     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12066     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12067
12068     if (pn >= 2) {
12069         if (fenMode || line[0] == '#') pn--;
12070         while (pn > 0) {
12071             /* skip positions before number pn */
12072             if (fgets(line, MSG_SIZ, f) == NULL) {
12073                 Reset(TRUE, TRUE);
12074                 DisplayError(_("Position not found in file"), 0);
12075                 return FALSE;
12076             }
12077             if (fenMode || line[0] == '#') pn--;
12078         }
12079     }
12080
12081     if (fenMode) {
12082         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12083             DisplayError(_("Bad FEN position in file"), 0);
12084             return FALSE;
12085         }
12086     } else {
12087         (void) fgets(line, MSG_SIZ, f);
12088         (void) fgets(line, MSG_SIZ, f);
12089
12090         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12091             (void) fgets(line, MSG_SIZ, f);
12092             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12093                 if (*p == ' ')
12094                   continue;
12095                 initial_position[i][j++] = CharToPiece(*p);
12096             }
12097         }
12098
12099         blackPlaysFirst = FALSE;
12100         if (!feof(f)) {
12101             (void) fgets(line, MSG_SIZ, f);
12102             if (strncmp(line, "black", strlen("black"))==0)
12103               blackPlaysFirst = TRUE;
12104         }
12105     }
12106     startedFromSetupPosition = TRUE;
12107
12108     CopyBoard(boards[0], initial_position);
12109     if (blackPlaysFirst) {
12110         currentMove = forwardMostMove = backwardMostMove = 1;
12111         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12112         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12113         CopyBoard(boards[1], initial_position);
12114         DisplayMessage("", _("Black to play"));
12115     } else {
12116         currentMove = forwardMostMove = backwardMostMove = 0;
12117         DisplayMessage("", _("White to play"));
12118     }
12119     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12120     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12121         SendToProgram("force\n", &first);
12122         SendBoard(&first, forwardMostMove);
12123     }
12124     if (appData.debugMode) {
12125 int i, j;
12126   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12127   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12128         fprintf(debugFP, "Load Position\n");
12129     }
12130
12131     if (positionNumber > 1) {
12132       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12133         DisplayTitle(line);
12134     } else {
12135         DisplayTitle(title);
12136     }
12137     gameMode = EditGame;
12138     ModeHighlight();
12139     ResetClocks();
12140     timeRemaining[0][1] = whiteTimeRemaining;
12141     timeRemaining[1][1] = blackTimeRemaining;
12142     DrawPosition(FALSE, boards[currentMove]);
12143
12144     return TRUE;
12145 }
12146
12147
12148 void
12149 CopyPlayerNameIntoFileName(dest, src)
12150      char **dest, *src;
12151 {
12152     while (*src != NULLCHAR && *src != ',') {
12153         if (*src == ' ') {
12154             *(*dest)++ = '_';
12155             src++;
12156         } else {
12157             *(*dest)++ = *src++;
12158         }
12159     }
12160 }
12161
12162 char *DefaultFileName(ext)
12163      char *ext;
12164 {
12165     static char def[MSG_SIZ];
12166     char *p;
12167
12168     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12169         p = def;
12170         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12171         *p++ = '-';
12172         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12173         *p++ = '.';
12174         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12175     } else {
12176         def[0] = NULLCHAR;
12177     }
12178     return def;
12179 }
12180
12181 /* Save the current game to the given file */
12182 int
12183 SaveGameToFile(filename, append)
12184      char *filename;
12185      int append;
12186 {
12187     FILE *f;
12188     char buf[MSG_SIZ];
12189     int result, i, t,tot=0;
12190
12191     if (strcmp(filename, "-") == 0) {
12192         return SaveGame(stdout, 0, NULL);
12193     } else {
12194         for(i=0; i<10; i++) { // upto 10 tries
12195              f = fopen(filename, append ? "a" : "w");
12196              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12197              if(f || errno != 13) break;
12198              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12199              tot += t;
12200         }
12201         if (f == NULL) {
12202             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12203             DisplayError(buf, errno);
12204             return FALSE;
12205         } else {
12206             safeStrCpy(buf, lastMsg, MSG_SIZ);
12207             DisplayMessage(_("Waiting for access to save file"), "");
12208             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12209             DisplayMessage(_("Saving game"), "");
12210             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12211             result = SaveGame(f, 0, NULL);
12212             DisplayMessage(buf, "");
12213             return result;
12214         }
12215     }
12216 }
12217
12218 char *
12219 SavePart(str)
12220      char *str;
12221 {
12222     static char buf[MSG_SIZ];
12223     char *p;
12224
12225     p = strchr(str, ' ');
12226     if (p == NULL) return str;
12227     strncpy(buf, str, p - str);
12228     buf[p - str] = NULLCHAR;
12229     return buf;
12230 }
12231
12232 #define PGN_MAX_LINE 75
12233
12234 #define PGN_SIDE_WHITE  0
12235 #define PGN_SIDE_BLACK  1
12236
12237 /* [AS] */
12238 static int FindFirstMoveOutOfBook( int side )
12239 {
12240     int result = -1;
12241
12242     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12243         int index = backwardMostMove;
12244         int has_book_hit = 0;
12245
12246         if( (index % 2) != side ) {
12247             index++;
12248         }
12249
12250         while( index < forwardMostMove ) {
12251             /* Check to see if engine is in book */
12252             int depth = pvInfoList[index].depth;
12253             int score = pvInfoList[index].score;
12254             int in_book = 0;
12255
12256             if( depth <= 2 ) {
12257                 in_book = 1;
12258             }
12259             else if( score == 0 && depth == 63 ) {
12260                 in_book = 1; /* Zappa */
12261             }
12262             else if( score == 2 && depth == 99 ) {
12263                 in_book = 1; /* Abrok */
12264             }
12265
12266             has_book_hit += in_book;
12267
12268             if( ! in_book ) {
12269                 result = index;
12270
12271                 break;
12272             }
12273
12274             index += 2;
12275         }
12276     }
12277
12278     return result;
12279 }
12280
12281 /* [AS] */
12282 void GetOutOfBookInfo( char * buf )
12283 {
12284     int oob[2];
12285     int i;
12286     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12287
12288     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12289     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12290
12291     *buf = '\0';
12292
12293     if( oob[0] >= 0 || oob[1] >= 0 ) {
12294         for( i=0; i<2; i++ ) {
12295             int idx = oob[i];
12296
12297             if( idx >= 0 ) {
12298                 if( i > 0 && oob[0] >= 0 ) {
12299                     strcat( buf, "   " );
12300                 }
12301
12302                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12303                 sprintf( buf+strlen(buf), "%s%.2f",
12304                     pvInfoList[idx].score >= 0 ? "+" : "",
12305                     pvInfoList[idx].score / 100.0 );
12306             }
12307         }
12308     }
12309 }
12310
12311 /* Save game in PGN style and close the file */
12312 int
12313 SaveGamePGN(f)
12314      FILE *f;
12315 {
12316     int i, offset, linelen, newblock;
12317     time_t tm;
12318 //    char *movetext;
12319     char numtext[32];
12320     int movelen, numlen, blank;
12321     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12322
12323     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12324
12325     tm = time((time_t *) NULL);
12326
12327     PrintPGNTags(f, &gameInfo);
12328
12329     if (backwardMostMove > 0 || startedFromSetupPosition) {
12330         char *fen = PositionToFEN(backwardMostMove, NULL);
12331         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12332         fprintf(f, "\n{--------------\n");
12333         PrintPosition(f, backwardMostMove);
12334         fprintf(f, "--------------}\n");
12335         free(fen);
12336     }
12337     else {
12338         /* [AS] Out of book annotation */
12339         if( appData.saveOutOfBookInfo ) {
12340             char buf[64];
12341
12342             GetOutOfBookInfo( buf );
12343
12344             if( buf[0] != '\0' ) {
12345                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12346             }
12347         }
12348
12349         fprintf(f, "\n");
12350     }
12351
12352     i = backwardMostMove;
12353     linelen = 0;
12354     newblock = TRUE;
12355
12356     while (i < forwardMostMove) {
12357         /* Print comments preceding this move */
12358         if (commentList[i] != NULL) {
12359             if (linelen > 0) fprintf(f, "\n");
12360             fprintf(f, "%s", commentList[i]);
12361             linelen = 0;
12362             newblock = TRUE;
12363         }
12364
12365         /* Format move number */
12366         if ((i % 2) == 0)
12367           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12368         else
12369           if (newblock)
12370             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12371           else
12372             numtext[0] = NULLCHAR;
12373
12374         numlen = strlen(numtext);
12375         newblock = FALSE;
12376
12377         /* Print move number */
12378         blank = linelen > 0 && numlen > 0;
12379         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12380             fprintf(f, "\n");
12381             linelen = 0;
12382             blank = 0;
12383         }
12384         if (blank) {
12385             fprintf(f, " ");
12386             linelen++;
12387         }
12388         fprintf(f, "%s", numtext);
12389         linelen += numlen;
12390
12391         /* Get move */
12392         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12393         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12394
12395         /* Print move */
12396         blank = linelen > 0 && movelen > 0;
12397         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12398             fprintf(f, "\n");
12399             linelen = 0;
12400             blank = 0;
12401         }
12402         if (blank) {
12403             fprintf(f, " ");
12404             linelen++;
12405         }
12406         fprintf(f, "%s", move_buffer);
12407         linelen += movelen;
12408
12409         /* [AS] Add PV info if present */
12410         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12411             /* [HGM] add time */
12412             char buf[MSG_SIZ]; int seconds;
12413
12414             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12415
12416             if( seconds <= 0)
12417               buf[0] = 0;
12418             else
12419               if( seconds < 30 )
12420                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12421               else
12422                 {
12423                   seconds = (seconds + 4)/10; // round to full seconds
12424                   if( seconds < 60 )
12425                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12426                   else
12427                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12428                 }
12429
12430             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12431                       pvInfoList[i].score >= 0 ? "+" : "",
12432                       pvInfoList[i].score / 100.0,
12433                       pvInfoList[i].depth,
12434                       buf );
12435
12436             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12437
12438             /* Print score/depth */
12439             blank = linelen > 0 && movelen > 0;
12440             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12441                 fprintf(f, "\n");
12442                 linelen = 0;
12443                 blank = 0;
12444             }
12445             if (blank) {
12446                 fprintf(f, " ");
12447                 linelen++;
12448             }
12449             fprintf(f, "%s", move_buffer);
12450             linelen += movelen;
12451         }
12452
12453         i++;
12454     }
12455
12456     /* Start a new line */
12457     if (linelen > 0) fprintf(f, "\n");
12458
12459     /* Print comments after last move */
12460     if (commentList[i] != NULL) {
12461         fprintf(f, "%s\n", commentList[i]);
12462     }
12463
12464     /* Print result */
12465     if (gameInfo.resultDetails != NULL &&
12466         gameInfo.resultDetails[0] != NULLCHAR) {
12467         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12468                 PGNResult(gameInfo.result));
12469     } else {
12470         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12471     }
12472
12473     fclose(f);
12474     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12475     return TRUE;
12476 }
12477
12478 /* Save game in old style and close the file */
12479 int
12480 SaveGameOldStyle(f)
12481      FILE *f;
12482 {
12483     int i, offset;
12484     time_t tm;
12485
12486     tm = time((time_t *) NULL);
12487
12488     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12489     PrintOpponents(f);
12490
12491     if (backwardMostMove > 0 || startedFromSetupPosition) {
12492         fprintf(f, "\n[--------------\n");
12493         PrintPosition(f, backwardMostMove);
12494         fprintf(f, "--------------]\n");
12495     } else {
12496         fprintf(f, "\n");
12497     }
12498
12499     i = backwardMostMove;
12500     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12501
12502     while (i < forwardMostMove) {
12503         if (commentList[i] != NULL) {
12504             fprintf(f, "[%s]\n", commentList[i]);
12505         }
12506
12507         if ((i % 2) == 1) {
12508             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12509             i++;
12510         } else {
12511             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12512             i++;
12513             if (commentList[i] != NULL) {
12514                 fprintf(f, "\n");
12515                 continue;
12516             }
12517             if (i >= forwardMostMove) {
12518                 fprintf(f, "\n");
12519                 break;
12520             }
12521             fprintf(f, "%s\n", parseList[i]);
12522             i++;
12523         }
12524     }
12525
12526     if (commentList[i] != NULL) {
12527         fprintf(f, "[%s]\n", commentList[i]);
12528     }
12529
12530     /* This isn't really the old style, but it's close enough */
12531     if (gameInfo.resultDetails != NULL &&
12532         gameInfo.resultDetails[0] != NULLCHAR) {
12533         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12534                 gameInfo.resultDetails);
12535     } else {
12536         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12537     }
12538
12539     fclose(f);
12540     return TRUE;
12541 }
12542
12543 /* Save the current game to open file f and close the file */
12544 int
12545 SaveGame(f, dummy, dummy2)
12546      FILE *f;
12547      int dummy;
12548      char *dummy2;
12549 {
12550     if (gameMode == EditPosition) EditPositionDone(TRUE);
12551     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12552     if (appData.oldSaveStyle)
12553       return SaveGameOldStyle(f);
12554     else
12555       return SaveGamePGN(f);
12556 }
12557
12558 /* Save the current position to the given file */
12559 int
12560 SavePositionToFile(filename)
12561      char *filename;
12562 {
12563     FILE *f;
12564     char buf[MSG_SIZ];
12565
12566     if (strcmp(filename, "-") == 0) {
12567         return SavePosition(stdout, 0, NULL);
12568     } else {
12569         f = fopen(filename, "a");
12570         if (f == NULL) {
12571             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12572             DisplayError(buf, errno);
12573             return FALSE;
12574         } else {
12575             safeStrCpy(buf, lastMsg, MSG_SIZ);
12576             DisplayMessage(_("Waiting for access to save file"), "");
12577             flock(fileno(f), LOCK_EX); // [HGM] lock
12578             DisplayMessage(_("Saving position"), "");
12579             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12580             SavePosition(f, 0, NULL);
12581             DisplayMessage(buf, "");
12582             return TRUE;
12583         }
12584     }
12585 }
12586
12587 /* Save the current position to the given open file and close the file */
12588 int
12589 SavePosition(f, dummy, dummy2)
12590      FILE *f;
12591      int dummy;
12592      char *dummy2;
12593 {
12594     time_t tm;
12595     char *fen;
12596
12597     if (gameMode == EditPosition) EditPositionDone(TRUE);
12598     if (appData.oldSaveStyle) {
12599         tm = time((time_t *) NULL);
12600
12601         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12602         PrintOpponents(f);
12603         fprintf(f, "[--------------\n");
12604         PrintPosition(f, currentMove);
12605         fprintf(f, "--------------]\n");
12606     } else {
12607         fen = PositionToFEN(currentMove, NULL);
12608         fprintf(f, "%s\n", fen);
12609         free(fen);
12610     }
12611     fclose(f);
12612     return TRUE;
12613 }
12614
12615 void
12616 ReloadCmailMsgEvent(unregister)
12617      int unregister;
12618 {
12619 #if !WIN32
12620     static char *inFilename = NULL;
12621     static char *outFilename;
12622     int i;
12623     struct stat inbuf, outbuf;
12624     int status;
12625
12626     /* Any registered moves are unregistered if unregister is set, */
12627     /* i.e. invoked by the signal handler */
12628     if (unregister) {
12629         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12630             cmailMoveRegistered[i] = FALSE;
12631             if (cmailCommentList[i] != NULL) {
12632                 free(cmailCommentList[i]);
12633                 cmailCommentList[i] = NULL;
12634             }
12635         }
12636         nCmailMovesRegistered = 0;
12637     }
12638
12639     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12640         cmailResult[i] = CMAIL_NOT_RESULT;
12641     }
12642     nCmailResults = 0;
12643
12644     if (inFilename == NULL) {
12645         /* Because the filenames are static they only get malloced once  */
12646         /* and they never get freed                                      */
12647         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12648         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12649
12650         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12651         sprintf(outFilename, "%s.out", appData.cmailGameName);
12652     }
12653
12654     status = stat(outFilename, &outbuf);
12655     if (status < 0) {
12656         cmailMailedMove = FALSE;
12657     } else {
12658         status = stat(inFilename, &inbuf);
12659         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12660     }
12661
12662     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12663        counts the games, notes how each one terminated, etc.
12664
12665        It would be nice to remove this kludge and instead gather all
12666        the information while building the game list.  (And to keep it
12667        in the game list nodes instead of having a bunch of fixed-size
12668        parallel arrays.)  Note this will require getting each game's
12669        termination from the PGN tags, as the game list builder does
12670        not process the game moves.  --mann
12671        */
12672     cmailMsgLoaded = TRUE;
12673     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12674
12675     /* Load first game in the file or popup game menu */
12676     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12677
12678 #endif /* !WIN32 */
12679     return;
12680 }
12681
12682 int
12683 RegisterMove()
12684 {
12685     FILE *f;
12686     char string[MSG_SIZ];
12687
12688     if (   cmailMailedMove
12689         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12690         return TRUE;            /* Allow free viewing  */
12691     }
12692
12693     /* Unregister move to ensure that we don't leave RegisterMove        */
12694     /* with the move registered when the conditions for registering no   */
12695     /* longer hold                                                       */
12696     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12697         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12698         nCmailMovesRegistered --;
12699
12700         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12701           {
12702               free(cmailCommentList[lastLoadGameNumber - 1]);
12703               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12704           }
12705     }
12706
12707     if (cmailOldMove == -1) {
12708         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12709         return FALSE;
12710     }
12711
12712     if (currentMove > cmailOldMove + 1) {
12713         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12714         return FALSE;
12715     }
12716
12717     if (currentMove < cmailOldMove) {
12718         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12719         return FALSE;
12720     }
12721
12722     if (forwardMostMove > currentMove) {
12723         /* Silently truncate extra moves */
12724         TruncateGame();
12725     }
12726
12727     if (   (currentMove == cmailOldMove + 1)
12728         || (   (currentMove == cmailOldMove)
12729             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12730                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12731         if (gameInfo.result != GameUnfinished) {
12732             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12733         }
12734
12735         if (commentList[currentMove] != NULL) {
12736             cmailCommentList[lastLoadGameNumber - 1]
12737               = StrSave(commentList[currentMove]);
12738         }
12739         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12740
12741         if (appData.debugMode)
12742           fprintf(debugFP, "Saving %s for game %d\n",
12743                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12744
12745         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12746
12747         f = fopen(string, "w");
12748         if (appData.oldSaveStyle) {
12749             SaveGameOldStyle(f); /* also closes the file */
12750
12751             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12752             f = fopen(string, "w");
12753             SavePosition(f, 0, NULL); /* also closes the file */
12754         } else {
12755             fprintf(f, "{--------------\n");
12756             PrintPosition(f, currentMove);
12757             fprintf(f, "--------------}\n\n");
12758
12759             SaveGame(f, 0, NULL); /* also closes the file*/
12760         }
12761
12762         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12763         nCmailMovesRegistered ++;
12764     } else if (nCmailGames == 1) {
12765         DisplayError(_("You have not made a move yet"), 0);
12766         return FALSE;
12767     }
12768
12769     return TRUE;
12770 }
12771
12772 void
12773 MailMoveEvent()
12774 {
12775 #if !WIN32
12776     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12777     FILE *commandOutput;
12778     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12779     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12780     int nBuffers;
12781     int i;
12782     int archived;
12783     char *arcDir;
12784
12785     if (! cmailMsgLoaded) {
12786         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12787         return;
12788     }
12789
12790     if (nCmailGames == nCmailResults) {
12791         DisplayError(_("No unfinished games"), 0);
12792         return;
12793     }
12794
12795 #if CMAIL_PROHIBIT_REMAIL
12796     if (cmailMailedMove) {
12797       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);
12798         DisplayError(msg, 0);
12799         return;
12800     }
12801 #endif
12802
12803     if (! (cmailMailedMove || RegisterMove())) return;
12804
12805     if (   cmailMailedMove
12806         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12807       snprintf(string, MSG_SIZ, partCommandString,
12808                appData.debugMode ? " -v" : "", appData.cmailGameName);
12809         commandOutput = popen(string, "r");
12810
12811         if (commandOutput == NULL) {
12812             DisplayError(_("Failed to invoke cmail"), 0);
12813         } else {
12814             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12815                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12816             }
12817             if (nBuffers > 1) {
12818                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12819                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12820                 nBytes = MSG_SIZ - 1;
12821             } else {
12822                 (void) memcpy(msg, buffer, nBytes);
12823             }
12824             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12825
12826             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12827                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12828
12829                 archived = TRUE;
12830                 for (i = 0; i < nCmailGames; i ++) {
12831                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12832                         archived = FALSE;
12833                     }
12834                 }
12835                 if (   archived
12836                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12837                         != NULL)) {
12838                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12839                            arcDir,
12840                            appData.cmailGameName,
12841                            gameInfo.date);
12842                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12843                     cmailMsgLoaded = FALSE;
12844                 }
12845             }
12846
12847             DisplayInformation(msg);
12848             pclose(commandOutput);
12849         }
12850     } else {
12851         if ((*cmailMsg) != '\0') {
12852             DisplayInformation(cmailMsg);
12853         }
12854     }
12855
12856     return;
12857 #endif /* !WIN32 */
12858 }
12859
12860 char *
12861 CmailMsg()
12862 {
12863 #if WIN32
12864     return NULL;
12865 #else
12866     int  prependComma = 0;
12867     char number[5];
12868     char string[MSG_SIZ];       /* Space for game-list */
12869     int  i;
12870
12871     if (!cmailMsgLoaded) return "";
12872
12873     if (cmailMailedMove) {
12874       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12875     } else {
12876         /* Create a list of games left */
12877       snprintf(string, MSG_SIZ, "[");
12878         for (i = 0; i < nCmailGames; i ++) {
12879             if (! (   cmailMoveRegistered[i]
12880                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12881                 if (prependComma) {
12882                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12883                 } else {
12884                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12885                     prependComma = 1;
12886                 }
12887
12888                 strcat(string, number);
12889             }
12890         }
12891         strcat(string, "]");
12892
12893         if (nCmailMovesRegistered + nCmailResults == 0) {
12894             switch (nCmailGames) {
12895               case 1:
12896                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12897                 break;
12898
12899               case 2:
12900                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12901                 break;
12902
12903               default:
12904                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12905                          nCmailGames);
12906                 break;
12907             }
12908         } else {
12909             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12910               case 1:
12911                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12912                          string);
12913                 break;
12914
12915               case 0:
12916                 if (nCmailResults == nCmailGames) {
12917                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12918                 } else {
12919                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12920                 }
12921                 break;
12922
12923               default:
12924                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12925                          string);
12926             }
12927         }
12928     }
12929     return cmailMsg;
12930 #endif /* WIN32 */
12931 }
12932
12933 void
12934 ResetGameEvent()
12935 {
12936     if (gameMode == Training)
12937       SetTrainingModeOff();
12938
12939     Reset(TRUE, TRUE);
12940     cmailMsgLoaded = FALSE;
12941     if (appData.icsActive) {
12942       SendToICS(ics_prefix);
12943       SendToICS("refresh\n");
12944     }
12945 }
12946
12947 void
12948 ExitEvent(status)
12949      int status;
12950 {
12951     exiting++;
12952     if (exiting > 2) {
12953       /* Give up on clean exit */
12954       exit(status);
12955     }
12956     if (exiting > 1) {
12957       /* Keep trying for clean exit */
12958       return;
12959     }
12960
12961     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12962
12963     if (telnetISR != NULL) {
12964       RemoveInputSource(telnetISR);
12965     }
12966     if (icsPR != NoProc) {
12967       DestroyChildProcess(icsPR, TRUE);
12968     }
12969
12970     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12971     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12972
12973     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12974     /* make sure this other one finishes before killing it!                  */
12975     if(endingGame) { int count = 0;
12976         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12977         while(endingGame && count++ < 10) DoSleep(1);
12978         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12979     }
12980
12981     /* Kill off chess programs */
12982     if (first.pr != NoProc) {
12983         ExitAnalyzeMode();
12984
12985         DoSleep( appData.delayBeforeQuit );
12986         SendToProgram("quit\n", &first);
12987         DoSleep( appData.delayAfterQuit );
12988         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12989     }
12990     if (second.pr != NoProc) {
12991         DoSleep( appData.delayBeforeQuit );
12992         SendToProgram("quit\n", &second);
12993         DoSleep( appData.delayAfterQuit );
12994         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12995     }
12996     if (first.isr != NULL) {
12997         RemoveInputSource(first.isr);
12998     }
12999     if (second.isr != NULL) {
13000         RemoveInputSource(second.isr);
13001     }
13002
13003     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13004     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13005
13006     ShutDownFrontEnd();
13007     exit(status);
13008 }
13009
13010 void
13011 PauseEvent()
13012 {
13013     if (appData.debugMode)
13014         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13015     if (pausing) {
13016         pausing = FALSE;
13017         ModeHighlight();
13018         if (gameMode == MachinePlaysWhite ||
13019             gameMode == MachinePlaysBlack) {
13020             StartClocks();
13021         } else {
13022             DisplayBothClocks();
13023         }
13024         if (gameMode == PlayFromGameFile) {
13025             if (appData.timeDelay >= 0)
13026                 AutoPlayGameLoop();
13027         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13028             Reset(FALSE, TRUE);
13029             SendToICS(ics_prefix);
13030             SendToICS("refresh\n");
13031         } else if (currentMove < forwardMostMove) {
13032             ForwardInner(forwardMostMove);
13033         }
13034         pauseExamInvalid = FALSE;
13035     } else {
13036         switch (gameMode) {
13037           default:
13038             return;
13039           case IcsExamining:
13040             pauseExamForwardMostMove = forwardMostMove;
13041             pauseExamInvalid = FALSE;
13042             /* fall through */
13043           case IcsObserving:
13044           case IcsPlayingWhite:
13045           case IcsPlayingBlack:
13046             pausing = TRUE;
13047             ModeHighlight();
13048             return;
13049           case PlayFromGameFile:
13050             (void) StopLoadGameTimer();
13051             pausing = TRUE;
13052             ModeHighlight();
13053             break;
13054           case BeginningOfGame:
13055             if (appData.icsActive) return;
13056             /* else fall through */
13057           case MachinePlaysWhite:
13058           case MachinePlaysBlack:
13059           case TwoMachinesPlay:
13060             if (forwardMostMove == 0)
13061               return;           /* don't pause if no one has moved */
13062             if ((gameMode == MachinePlaysWhite &&
13063                  !WhiteOnMove(forwardMostMove)) ||
13064                 (gameMode == MachinePlaysBlack &&
13065                  WhiteOnMove(forwardMostMove))) {
13066                 StopClocks();
13067             }
13068             pausing = TRUE;
13069             ModeHighlight();
13070             break;
13071         }
13072     }
13073 }
13074
13075 void
13076 EditCommentEvent()
13077 {
13078     char title[MSG_SIZ];
13079
13080     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13081       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13082     } else {
13083       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13084                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13085                parseList[currentMove - 1]);
13086     }
13087
13088     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13089 }
13090
13091
13092 void
13093 EditTagsEvent()
13094 {
13095     char *tags = PGNTags(&gameInfo);
13096     bookUp = FALSE;
13097     EditTagsPopUp(tags, NULL);
13098     free(tags);
13099 }
13100
13101 void
13102 AnalyzeModeEvent()
13103 {
13104     if (appData.noChessProgram || gameMode == AnalyzeMode)
13105       return;
13106
13107     if (gameMode != AnalyzeFile) {
13108         if (!appData.icsEngineAnalyze) {
13109                EditGameEvent();
13110                if (gameMode != EditGame) return;
13111         }
13112         ResurrectChessProgram();
13113         SendToProgram("analyze\n", &first);
13114         first.analyzing = TRUE;
13115         /*first.maybeThinking = TRUE;*/
13116         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13117         EngineOutputPopUp();
13118     }
13119     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13120     pausing = FALSE;
13121     ModeHighlight();
13122     SetGameInfo();
13123
13124     StartAnalysisClock();
13125     GetTimeMark(&lastNodeCountTime);
13126     lastNodeCount = 0;
13127 }
13128
13129 void
13130 AnalyzeFileEvent()
13131 {
13132     if (appData.noChessProgram || gameMode == AnalyzeFile)
13133       return;
13134
13135     if (gameMode != AnalyzeMode) {
13136         EditGameEvent();
13137         if (gameMode != EditGame) return;
13138         ResurrectChessProgram();
13139         SendToProgram("analyze\n", &first);
13140         first.analyzing = TRUE;
13141         /*first.maybeThinking = TRUE;*/
13142         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13143         EngineOutputPopUp();
13144     }
13145     gameMode = AnalyzeFile;
13146     pausing = FALSE;
13147     ModeHighlight();
13148     SetGameInfo();
13149
13150     StartAnalysisClock();
13151     GetTimeMark(&lastNodeCountTime);
13152     lastNodeCount = 0;
13153     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13154 }
13155
13156 void
13157 MachineWhiteEvent()
13158 {
13159     char buf[MSG_SIZ];
13160     char *bookHit = NULL;
13161
13162     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13163       return;
13164
13165
13166     if (gameMode == PlayFromGameFile ||
13167         gameMode == TwoMachinesPlay  ||
13168         gameMode == Training         ||
13169         gameMode == AnalyzeMode      ||
13170         gameMode == EndOfGame)
13171         EditGameEvent();
13172
13173     if (gameMode == EditPosition)
13174         EditPositionDone(TRUE);
13175
13176     if (!WhiteOnMove(currentMove)) {
13177         DisplayError(_("It is not White's turn"), 0);
13178         return;
13179     }
13180
13181     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13182       ExitAnalyzeMode();
13183
13184     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13185         gameMode == AnalyzeFile)
13186         TruncateGame();
13187
13188     ResurrectChessProgram();    /* in case it isn't running */
13189     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13190         gameMode = MachinePlaysWhite;
13191         ResetClocks();
13192     } else
13193     gameMode = MachinePlaysWhite;
13194     pausing = FALSE;
13195     ModeHighlight();
13196     SetGameInfo();
13197     snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
13198     DisplayTitle(buf);
13199     if (first.sendName) {
13200       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13201       SendToProgram(buf, &first);
13202     }
13203     if (first.sendTime) {
13204       if (first.useColors) {
13205         SendToProgram("black\n", &first); /*gnu kludge*/
13206       }
13207       SendTimeRemaining(&first, TRUE);
13208     }
13209     if (first.useColors) {
13210       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13211     }
13212     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13213     SetMachineThinkingEnables();
13214     first.maybeThinking = TRUE;
13215     StartClocks();
13216     firstMove = FALSE;
13217
13218     if (appData.autoFlipView && !flipView) {
13219       flipView = !flipView;
13220       DrawPosition(FALSE, NULL);
13221       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13222     }
13223
13224     if(bookHit) { // [HGM] book: simulate book reply
13225         static char bookMove[MSG_SIZ]; // a bit generous?
13226
13227         programStats.nodes = programStats.depth = programStats.time =
13228         programStats.score = programStats.got_only_move = 0;
13229         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13230
13231         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13232         strcat(bookMove, bookHit);
13233         HandleMachineMove(bookMove, &first);
13234     }
13235 }
13236
13237 void
13238 MachineBlackEvent()
13239 {
13240   char buf[MSG_SIZ];
13241   char *bookHit = NULL;
13242
13243     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13244         return;
13245
13246
13247     if (gameMode == PlayFromGameFile ||
13248         gameMode == TwoMachinesPlay  ||
13249         gameMode == Training         ||
13250         gameMode == AnalyzeMode      ||
13251         gameMode == EndOfGame)
13252         EditGameEvent();
13253
13254     if (gameMode == EditPosition)
13255         EditPositionDone(TRUE);
13256
13257     if (WhiteOnMove(currentMove)) {
13258         DisplayError(_("It is not Black's turn"), 0);
13259         return;
13260     }
13261
13262     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13263       ExitAnalyzeMode();
13264
13265     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13266         gameMode == AnalyzeFile)
13267         TruncateGame();
13268
13269     ResurrectChessProgram();    /* in case it isn't running */
13270     gameMode = MachinePlaysBlack;
13271     pausing = FALSE;
13272     ModeHighlight();
13273     SetGameInfo();
13274     snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
13275     DisplayTitle(buf);
13276     if (first.sendName) {
13277       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13278       SendToProgram(buf, &first);
13279     }
13280     if (first.sendTime) {
13281       if (first.useColors) {
13282         SendToProgram("white\n", &first); /*gnu kludge*/
13283       }
13284       SendTimeRemaining(&first, FALSE);
13285     }
13286     if (first.useColors) {
13287       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13288     }
13289     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13290     SetMachineThinkingEnables();
13291     first.maybeThinking = TRUE;
13292     StartClocks();
13293
13294     if (appData.autoFlipView && flipView) {
13295       flipView = !flipView;
13296       DrawPosition(FALSE, NULL);
13297       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13298     }
13299     if(bookHit) { // [HGM] book: simulate book reply
13300         static char bookMove[MSG_SIZ]; // a bit generous?
13301
13302         programStats.nodes = programStats.depth = programStats.time =
13303         programStats.score = programStats.got_only_move = 0;
13304         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13305
13306         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13307         strcat(bookMove, bookHit);
13308         HandleMachineMove(bookMove, &first);
13309     }
13310 }
13311
13312
13313 void
13314 DisplayTwoMachinesTitle()
13315 {
13316     char buf[MSG_SIZ];
13317     if (appData.matchGames > 0) {
13318         if(appData.tourneyFile[0]) {
13319           snprintf(buf, MSG_SIZ, _("%s vs. %s (%d/%d%s)"),
13320                    gameInfo.white, gameInfo.black,
13321                    nextGame+1, appData.matchGames+1,
13322                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13323         } else 
13324         if (first.twoMachinesColor[0] == 'w') {
13325           snprintf(buf, MSG_SIZ, _("%s vs. %s (%d-%d-%d)"),
13326                    gameInfo.white, gameInfo.black,
13327                    first.matchWins, second.matchWins,
13328                    matchGame - 1 - (first.matchWins + second.matchWins));
13329         } else {
13330           snprintf(buf, MSG_SIZ, _("%s vs. %s (%d-%d-%d)"),
13331                    gameInfo.white, gameInfo.black,
13332                    second.matchWins, first.matchWins,
13333                    matchGame - 1 - (first.matchWins + second.matchWins));
13334         }
13335     } else {
13336       snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
13337     }
13338     DisplayTitle(buf);
13339 }
13340
13341 void
13342 SettingsMenuIfReady()
13343 {
13344   if (second.lastPing != second.lastPong) {
13345     DisplayMessage("", _("Waiting for second chess program"));
13346     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13347     return;
13348   }
13349   ThawUI();
13350   DisplayMessage("", "");
13351   SettingsPopUp(&second);
13352 }
13353
13354 int
13355 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
13356 {
13357     char buf[MSG_SIZ];
13358     if (cps->pr == NoProc) {
13359         StartChessProgram(cps);
13360         if (cps->protocolVersion == 1) {
13361           retry();
13362         } else {
13363           /* kludge: allow timeout for initial "feature" command */
13364           FreezeUI();
13365           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13366           DisplayMessage("", buf);
13367           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13368         }
13369         return 1;
13370     }
13371     return 0;
13372 }
13373
13374 void
13375 TwoMachinesEvent P((void))
13376 {
13377     int i;
13378     char buf[MSG_SIZ];
13379     ChessProgramState *onmove;
13380     char *bookHit = NULL;
13381     static int stalling = 0;
13382     TimeMark now;
13383     long wait;
13384
13385     if (appData.noChessProgram) return;
13386
13387     switch (gameMode) {
13388       case TwoMachinesPlay:
13389         return;
13390       case MachinePlaysWhite:
13391       case MachinePlaysBlack:
13392         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13393             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13394             return;
13395         }
13396         /* fall through */
13397       case BeginningOfGame:
13398       case PlayFromGameFile:
13399       case EndOfGame:
13400         EditGameEvent();
13401         if (gameMode != EditGame) return;
13402         break;
13403       case EditPosition:
13404         EditPositionDone(TRUE);
13405         break;
13406       case AnalyzeMode:
13407       case AnalyzeFile:
13408         ExitAnalyzeMode();
13409         break;
13410       case EditGame:
13411       default:
13412         break;
13413     }
13414
13415 //    forwardMostMove = currentMove;
13416     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13417
13418     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13419
13420     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13421     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13422       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13423       return;
13424     }
13425     if(!stalling) {
13426       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13427       SendToProgram("force\n", &second);
13428       stalling = 1;
13429       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13430       return;
13431     }
13432     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13433     if(appData.matchPause>10000 || appData.matchPause<10)
13434                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13435     wait = SubtractTimeMarks(&now, &pauseStart);
13436     if(wait < appData.matchPause) {
13437         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13438         return;
13439     }
13440     stalling = 0;
13441     DisplayMessage("", "");
13442     if (startedFromSetupPosition) {
13443         SendBoard(&second, backwardMostMove);
13444     if (appData.debugMode) {
13445         fprintf(debugFP, "Two Machines\n");
13446     }
13447     }
13448     for (i = backwardMostMove; i < forwardMostMove; i++) {
13449         SendMoveToProgram(i, &second);
13450     }
13451
13452     gameMode = TwoMachinesPlay;
13453     pausing = FALSE;
13454     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13455     SetGameInfo();
13456     DisplayTwoMachinesTitle();
13457     firstMove = TRUE;
13458     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13459         onmove = &first;
13460     } else {
13461         onmove = &second;
13462     }
13463     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13464     SendToProgram(first.computerString, &first);
13465     if (first.sendName) {
13466       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13467       SendToProgram(buf, &first);
13468     }
13469     SendToProgram(second.computerString, &second);
13470     if (second.sendName) {
13471       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13472       SendToProgram(buf, &second);
13473     }
13474
13475     ResetClocks();
13476     if (!first.sendTime || !second.sendTime) {
13477         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13478         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13479     }
13480     if (onmove->sendTime) {
13481       if (onmove->useColors) {
13482         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13483       }
13484       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13485     }
13486     if (onmove->useColors) {
13487       SendToProgram(onmove->twoMachinesColor, onmove);
13488     }
13489     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13490 //    SendToProgram("go\n", onmove);
13491     onmove->maybeThinking = TRUE;
13492     SetMachineThinkingEnables();
13493
13494     StartClocks();
13495
13496     if(bookHit) { // [HGM] book: simulate book reply
13497         static char bookMove[MSG_SIZ]; // a bit generous?
13498
13499         programStats.nodes = programStats.depth = programStats.time =
13500         programStats.score = programStats.got_only_move = 0;
13501         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13502
13503         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13504         strcat(bookMove, bookHit);
13505         savedMessage = bookMove; // args for deferred call
13506         savedState = onmove;
13507         ScheduleDelayedEvent(DeferredBookMove, 1);
13508     }
13509 }
13510
13511 void
13512 TrainingEvent()
13513 {
13514     if (gameMode == Training) {
13515       SetTrainingModeOff();
13516       gameMode = PlayFromGameFile;
13517       DisplayMessage("", _("Training mode off"));
13518     } else {
13519       gameMode = Training;
13520       animateTraining = appData.animate;
13521
13522       /* make sure we are not already at the end of the game */
13523       if (currentMove < forwardMostMove) {
13524         SetTrainingModeOn();
13525         DisplayMessage("", _("Training mode on"));
13526       } else {
13527         gameMode = PlayFromGameFile;
13528         DisplayError(_("Already at end of game"), 0);
13529       }
13530     }
13531     ModeHighlight();
13532 }
13533
13534 void
13535 IcsClientEvent()
13536 {
13537     if (!appData.icsActive) return;
13538     switch (gameMode) {
13539       case IcsPlayingWhite:
13540       case IcsPlayingBlack:
13541       case IcsObserving:
13542       case IcsIdle:
13543       case BeginningOfGame:
13544       case IcsExamining:
13545         return;
13546
13547       case EditGame:
13548         break;
13549
13550       case EditPosition:
13551         EditPositionDone(TRUE);
13552         break;
13553
13554       case AnalyzeMode:
13555       case AnalyzeFile:
13556         ExitAnalyzeMode();
13557         break;
13558
13559       default:
13560         EditGameEvent();
13561         break;
13562     }
13563
13564     gameMode = IcsIdle;
13565     ModeHighlight();
13566     return;
13567 }
13568
13569
13570 void
13571 EditGameEvent()
13572 {
13573     int i;
13574
13575     switch (gameMode) {
13576       case Training:
13577         SetTrainingModeOff();
13578         break;
13579       case MachinePlaysWhite:
13580       case MachinePlaysBlack:
13581       case BeginningOfGame:
13582         SendToProgram("force\n", &first);
13583         SetUserThinkingEnables();
13584         break;
13585       case PlayFromGameFile:
13586         (void) StopLoadGameTimer();
13587         if (gameFileFP != NULL) {
13588             gameFileFP = NULL;
13589         }
13590         break;
13591       case EditPosition:
13592         EditPositionDone(TRUE);
13593         break;
13594       case AnalyzeMode:
13595       case AnalyzeFile:
13596         ExitAnalyzeMode();
13597         SendToProgram("force\n", &first);
13598         break;
13599       case TwoMachinesPlay:
13600         GameEnds(EndOfFile, NULL, GE_PLAYER);
13601         ResurrectChessProgram();
13602         SetUserThinkingEnables();
13603         break;
13604       case EndOfGame:
13605         ResurrectChessProgram();
13606         break;
13607       case IcsPlayingBlack:
13608       case IcsPlayingWhite:
13609         DisplayError(_("Warning: You are still playing a game"), 0);
13610         break;
13611       case IcsObserving:
13612         DisplayError(_("Warning: You are still observing a game"), 0);
13613         break;
13614       case IcsExamining:
13615         DisplayError(_("Warning: You are still examining a game"), 0);
13616         break;
13617       case IcsIdle:
13618         break;
13619       case EditGame:
13620       default:
13621         return;
13622     }
13623
13624     pausing = FALSE;
13625     StopClocks();
13626     first.offeredDraw = second.offeredDraw = 0;
13627
13628     if (gameMode == PlayFromGameFile) {
13629         whiteTimeRemaining = timeRemaining[0][currentMove];
13630         blackTimeRemaining = timeRemaining[1][currentMove];
13631         DisplayTitle("");
13632     }
13633
13634     if (gameMode == MachinePlaysWhite ||
13635         gameMode == MachinePlaysBlack ||
13636         gameMode == TwoMachinesPlay ||
13637         gameMode == EndOfGame) {
13638         i = forwardMostMove;
13639         while (i > currentMove) {
13640             SendToProgram("undo\n", &first);
13641             i--;
13642         }
13643         if(!adjustedClock) {
13644         whiteTimeRemaining = timeRemaining[0][currentMove];
13645         blackTimeRemaining = timeRemaining[1][currentMove];
13646         DisplayBothClocks();
13647         }
13648         if (whiteFlag || blackFlag) {
13649             whiteFlag = blackFlag = 0;
13650         }
13651         DisplayTitle("");
13652     }
13653
13654     gameMode = EditGame;
13655     ModeHighlight();
13656     SetGameInfo();
13657 }
13658
13659
13660 void
13661 EditPositionEvent()
13662 {
13663     if (gameMode == EditPosition) {
13664         EditGameEvent();
13665         return;
13666     }
13667
13668     EditGameEvent();
13669     if (gameMode != EditGame) return;
13670
13671     gameMode = EditPosition;
13672     ModeHighlight();
13673     SetGameInfo();
13674     if (currentMove > 0)
13675       CopyBoard(boards[0], boards[currentMove]);
13676
13677     blackPlaysFirst = !WhiteOnMove(currentMove);
13678     ResetClocks();
13679     currentMove = forwardMostMove = backwardMostMove = 0;
13680     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13681     DisplayMove(-1);
13682 }
13683
13684 void
13685 ExitAnalyzeMode()
13686 {
13687     /* [DM] icsEngineAnalyze - possible call from other functions */
13688     if (appData.icsEngineAnalyze) {
13689         appData.icsEngineAnalyze = FALSE;
13690
13691         DisplayMessage("",_("Close ICS engine analyze..."));
13692     }
13693     if (first.analysisSupport && first.analyzing) {
13694       SendToProgram("exit\n", &first);
13695       first.analyzing = FALSE;
13696     }
13697     thinkOutput[0] = NULLCHAR;
13698 }
13699
13700 void
13701 EditPositionDone(Boolean fakeRights)
13702 {
13703     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13704
13705     startedFromSetupPosition = TRUE;
13706     InitChessProgram(&first, FALSE);
13707     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13708       boards[0][EP_STATUS] = EP_NONE;
13709       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13710     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13711         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13712         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13713       } else boards[0][CASTLING][2] = NoRights;
13714     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13715         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13716         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13717       } else boards[0][CASTLING][5] = NoRights;
13718     }
13719     SendToProgram("force\n", &first);
13720     if (blackPlaysFirst) {
13721         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13722         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13723         currentMove = forwardMostMove = backwardMostMove = 1;
13724         CopyBoard(boards[1], boards[0]);
13725     } else {
13726         currentMove = forwardMostMove = backwardMostMove = 0;
13727     }
13728     SendBoard(&first, forwardMostMove);
13729     if (appData.debugMode) {
13730         fprintf(debugFP, "EditPosDone\n");
13731     }
13732     DisplayTitle("");
13733     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13734     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13735     gameMode = EditGame;
13736     ModeHighlight();
13737     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13738     ClearHighlights(); /* [AS] */
13739 }
13740
13741 /* Pause for `ms' milliseconds */
13742 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13743 void
13744 TimeDelay(ms)
13745      long ms;
13746 {
13747     TimeMark m1, m2;
13748
13749     GetTimeMark(&m1);
13750     do {
13751         GetTimeMark(&m2);
13752     } while (SubtractTimeMarks(&m2, &m1) < ms);
13753 }
13754
13755 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13756 void
13757 SendMultiLineToICS(buf)
13758      char *buf;
13759 {
13760     char temp[MSG_SIZ+1], *p;
13761     int len;
13762
13763     len = strlen(buf);
13764     if (len > MSG_SIZ)
13765       len = MSG_SIZ;
13766
13767     strncpy(temp, buf, len);
13768     temp[len] = 0;
13769
13770     p = temp;
13771     while (*p) {
13772         if (*p == '\n' || *p == '\r')
13773           *p = ' ';
13774         ++p;
13775     }
13776
13777     strcat(temp, "\n");
13778     SendToICS(temp);
13779     SendToPlayer(temp, strlen(temp));
13780 }
13781
13782 void
13783 SetWhiteToPlayEvent()
13784 {
13785     if (gameMode == EditPosition) {
13786         blackPlaysFirst = FALSE;
13787         DisplayBothClocks();    /* works because currentMove is 0 */
13788     } else if (gameMode == IcsExamining) {
13789         SendToICS(ics_prefix);
13790         SendToICS("tomove white\n");
13791     }
13792 }
13793
13794 void
13795 SetBlackToPlayEvent()
13796 {
13797     if (gameMode == EditPosition) {
13798         blackPlaysFirst = TRUE;
13799         currentMove = 1;        /* kludge */
13800         DisplayBothClocks();
13801         currentMove = 0;
13802     } else if (gameMode == IcsExamining) {
13803         SendToICS(ics_prefix);
13804         SendToICS("tomove black\n");
13805     }
13806 }
13807
13808 void
13809 EditPositionMenuEvent(selection, x, y)
13810      ChessSquare selection;
13811      int x, y;
13812 {
13813     char buf[MSG_SIZ];
13814     ChessSquare piece = boards[0][y][x];
13815
13816     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13817
13818     switch (selection) {
13819       case ClearBoard:
13820         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13821             SendToICS(ics_prefix);
13822             SendToICS("bsetup clear\n");
13823         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13824             SendToICS(ics_prefix);
13825             SendToICS("clearboard\n");
13826         } else {
13827             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13828                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13829                 for (y = 0; y < BOARD_HEIGHT; y++) {
13830                     if (gameMode == IcsExamining) {
13831                         if (boards[currentMove][y][x] != EmptySquare) {
13832                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13833                                     AAA + x, ONE + y);
13834                             SendToICS(buf);
13835                         }
13836                     } else {
13837                         boards[0][y][x] = p;
13838                     }
13839                 }
13840             }
13841         }
13842         if (gameMode == EditPosition) {
13843             DrawPosition(FALSE, boards[0]);
13844         }
13845         break;
13846
13847       case WhitePlay:
13848         SetWhiteToPlayEvent();
13849         break;
13850
13851       case BlackPlay:
13852         SetBlackToPlayEvent();
13853         break;
13854
13855       case EmptySquare:
13856         if (gameMode == IcsExamining) {
13857             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13858             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13859             SendToICS(buf);
13860         } else {
13861             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13862                 if(x == BOARD_LEFT-2) {
13863                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13864                     boards[0][y][1] = 0;
13865                 } else
13866                 if(x == BOARD_RGHT+1) {
13867                     if(y >= gameInfo.holdingsSize) break;
13868                     boards[0][y][BOARD_WIDTH-2] = 0;
13869                 } else break;
13870             }
13871             boards[0][y][x] = EmptySquare;
13872             DrawPosition(FALSE, boards[0]);
13873         }
13874         break;
13875
13876       case PromotePiece:
13877         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13878            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13879             selection = (ChessSquare) (PROMOTED piece);
13880         } else if(piece == EmptySquare) selection = WhiteSilver;
13881         else selection = (ChessSquare)((int)piece - 1);
13882         goto defaultlabel;
13883
13884       case DemotePiece:
13885         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13886            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13887             selection = (ChessSquare) (DEMOTED piece);
13888         } else if(piece == EmptySquare) selection = BlackSilver;
13889         else selection = (ChessSquare)((int)piece + 1);
13890         goto defaultlabel;
13891
13892       case WhiteQueen:
13893       case BlackQueen:
13894         if(gameInfo.variant == VariantShatranj ||
13895            gameInfo.variant == VariantXiangqi  ||
13896            gameInfo.variant == VariantCourier  ||
13897            gameInfo.variant == VariantMakruk     )
13898             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13899         goto defaultlabel;
13900
13901       case WhiteKing:
13902       case BlackKing:
13903         if(gameInfo.variant == VariantXiangqi)
13904             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13905         if(gameInfo.variant == VariantKnightmate)
13906             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13907       default:
13908         defaultlabel:
13909         if (gameMode == IcsExamining) {
13910             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13911             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13912                      PieceToChar(selection), AAA + x, ONE + y);
13913             SendToICS(buf);
13914         } else {
13915             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13916                 int n;
13917                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13918                     n = PieceToNumber(selection - BlackPawn);
13919                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13920                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13921                     boards[0][BOARD_HEIGHT-1-n][1]++;
13922                 } else
13923                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13924                     n = PieceToNumber(selection);
13925                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13926                     boards[0][n][BOARD_WIDTH-1] = selection;
13927                     boards[0][n][BOARD_WIDTH-2]++;
13928                 }
13929             } else
13930             boards[0][y][x] = selection;
13931             DrawPosition(TRUE, boards[0]);
13932         }
13933         break;
13934     }
13935 }
13936
13937
13938 void
13939 DropMenuEvent(selection, x, y)
13940      ChessSquare selection;
13941      int x, y;
13942 {
13943     ChessMove moveType;
13944
13945     switch (gameMode) {
13946       case IcsPlayingWhite:
13947       case MachinePlaysBlack:
13948         if (!WhiteOnMove(currentMove)) {
13949             DisplayMoveError(_("It is Black's turn"));
13950             return;
13951         }
13952         moveType = WhiteDrop;
13953         break;
13954       case IcsPlayingBlack:
13955       case MachinePlaysWhite:
13956         if (WhiteOnMove(currentMove)) {
13957             DisplayMoveError(_("It is White's turn"));
13958             return;
13959         }
13960         moveType = BlackDrop;
13961         break;
13962       case EditGame:
13963         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13964         break;
13965       default:
13966         return;
13967     }
13968
13969     if (moveType == BlackDrop && selection < BlackPawn) {
13970       selection = (ChessSquare) ((int) selection
13971                                  + (int) BlackPawn - (int) WhitePawn);
13972     }
13973     if (boards[currentMove][y][x] != EmptySquare) {
13974         DisplayMoveError(_("That square is occupied"));
13975         return;
13976     }
13977
13978     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13979 }
13980
13981 void
13982 AcceptEvent()
13983 {
13984     /* Accept a pending offer of any kind from opponent */
13985
13986     if (appData.icsActive) {
13987         SendToICS(ics_prefix);
13988         SendToICS("accept\n");
13989     } else if (cmailMsgLoaded) {
13990         if (currentMove == cmailOldMove &&
13991             commentList[cmailOldMove] != NULL &&
13992             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13993                    "Black offers a draw" : "White offers a draw")) {
13994             TruncateGame();
13995             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13996             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13997         } else {
13998             DisplayError(_("There is no pending offer on this move"), 0);
13999             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14000         }
14001     } else {
14002         /* Not used for offers from chess program */
14003     }
14004 }
14005
14006 void
14007 DeclineEvent()
14008 {
14009     /* Decline a pending offer of any kind from opponent */
14010
14011     if (appData.icsActive) {
14012         SendToICS(ics_prefix);
14013         SendToICS("decline\n");
14014     } else if (cmailMsgLoaded) {
14015         if (currentMove == cmailOldMove &&
14016             commentList[cmailOldMove] != NULL &&
14017             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14018                    "Black offers a draw" : "White offers a draw")) {
14019 #ifdef NOTDEF
14020             AppendComment(cmailOldMove, "Draw declined", TRUE);
14021             DisplayComment(cmailOldMove - 1, "Draw declined");
14022 #endif /*NOTDEF*/
14023         } else {
14024             DisplayError(_("There is no pending offer on this move"), 0);
14025         }
14026     } else {
14027         /* Not used for offers from chess program */
14028     }
14029 }
14030
14031 void
14032 RematchEvent()
14033 {
14034     /* Issue ICS rematch command */
14035     if (appData.icsActive) {
14036         SendToICS(ics_prefix);
14037         SendToICS("rematch\n");
14038     }
14039 }
14040
14041 void
14042 CallFlagEvent()
14043 {
14044     /* Call your opponent's flag (claim a win on time) */
14045     if (appData.icsActive) {
14046         SendToICS(ics_prefix);
14047         SendToICS("flag\n");
14048     } else {
14049         switch (gameMode) {
14050           default:
14051             return;
14052           case MachinePlaysWhite:
14053             if (whiteFlag) {
14054                 if (blackFlag)
14055                   GameEnds(GameIsDrawn, "Both players ran out of time",
14056                            GE_PLAYER);
14057                 else
14058                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14059             } else {
14060                 DisplayError(_("Your opponent is not out of time"), 0);
14061             }
14062             break;
14063           case MachinePlaysBlack:
14064             if (blackFlag) {
14065                 if (whiteFlag)
14066                   GameEnds(GameIsDrawn, "Both players ran out of time",
14067                            GE_PLAYER);
14068                 else
14069                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14070             } else {
14071                 DisplayError(_("Your opponent is not out of time"), 0);
14072             }
14073             break;
14074         }
14075     }
14076 }
14077
14078 void
14079 ClockClick(int which)
14080 {       // [HGM] code moved to back-end from winboard.c
14081         if(which) { // black clock
14082           if (gameMode == EditPosition || gameMode == IcsExamining) {
14083             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14084             SetBlackToPlayEvent();
14085           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14086           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14087           } else if (shiftKey) {
14088             AdjustClock(which, -1);
14089           } else if (gameMode == IcsPlayingWhite ||
14090                      gameMode == MachinePlaysBlack) {
14091             CallFlagEvent();
14092           }
14093         } else { // white clock
14094           if (gameMode == EditPosition || gameMode == IcsExamining) {
14095             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14096             SetWhiteToPlayEvent();
14097           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14098           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14099           } else if (shiftKey) {
14100             AdjustClock(which, -1);
14101           } else if (gameMode == IcsPlayingBlack ||
14102                    gameMode == MachinePlaysWhite) {
14103             CallFlagEvent();
14104           }
14105         }
14106 }
14107
14108 void
14109 DrawEvent()
14110 {
14111     /* Offer draw or accept pending draw offer from opponent */
14112
14113     if (appData.icsActive) {
14114         /* Note: tournament rules require draw offers to be
14115            made after you make your move but before you punch
14116            your clock.  Currently ICS doesn't let you do that;
14117            instead, you immediately punch your clock after making
14118            a move, but you can offer a draw at any time. */
14119
14120         SendToICS(ics_prefix);
14121         SendToICS("draw\n");
14122         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14123     } else if (cmailMsgLoaded) {
14124         if (currentMove == cmailOldMove &&
14125             commentList[cmailOldMove] != NULL &&
14126             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14127                    "Black offers a draw" : "White offers a draw")) {
14128             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14129             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14130         } else if (currentMove == cmailOldMove + 1) {
14131             char *offer = WhiteOnMove(cmailOldMove) ?
14132               "White offers a draw" : "Black offers a draw";
14133             AppendComment(currentMove, offer, TRUE);
14134             DisplayComment(currentMove - 1, offer);
14135             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14136         } else {
14137             DisplayError(_("You must make your move before offering a draw"), 0);
14138             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14139         }
14140     } else if (first.offeredDraw) {
14141         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14142     } else {
14143         if (first.sendDrawOffers) {
14144             SendToProgram("draw\n", &first);
14145             userOfferedDraw = TRUE;
14146         }
14147     }
14148 }
14149
14150 void
14151 AdjournEvent()
14152 {
14153     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14154
14155     if (appData.icsActive) {
14156         SendToICS(ics_prefix);
14157         SendToICS("adjourn\n");
14158     } else {
14159         /* Currently GNU Chess doesn't offer or accept Adjourns */
14160     }
14161 }
14162
14163
14164 void
14165 AbortEvent()
14166 {
14167     /* Offer Abort or accept pending Abort offer from opponent */
14168
14169     if (appData.icsActive) {
14170         SendToICS(ics_prefix);
14171         SendToICS("abort\n");
14172     } else {
14173         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14174     }
14175 }
14176
14177 void
14178 ResignEvent()
14179 {
14180     /* Resign.  You can do this even if it's not your turn. */
14181
14182     if (appData.icsActive) {
14183         SendToICS(ics_prefix);
14184         SendToICS("resign\n");
14185     } else {
14186         switch (gameMode) {
14187           case MachinePlaysWhite:
14188             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14189             break;
14190           case MachinePlaysBlack:
14191             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14192             break;
14193           case EditGame:
14194             if (cmailMsgLoaded) {
14195                 TruncateGame();
14196                 if (WhiteOnMove(cmailOldMove)) {
14197                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14198                 } else {
14199                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14200                 }
14201                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14202             }
14203             break;
14204           default:
14205             break;
14206         }
14207     }
14208 }
14209
14210
14211 void
14212 StopObservingEvent()
14213 {
14214     /* Stop observing current games */
14215     SendToICS(ics_prefix);
14216     SendToICS("unobserve\n");
14217 }
14218
14219 void
14220 StopExaminingEvent()
14221 {
14222     /* Stop observing current game */
14223     SendToICS(ics_prefix);
14224     SendToICS("unexamine\n");
14225 }
14226
14227 void
14228 ForwardInner(target)
14229      int target;
14230 {
14231     int limit;
14232
14233     if (appData.debugMode)
14234         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14235                 target, currentMove, forwardMostMove);
14236
14237     if (gameMode == EditPosition)
14238       return;
14239
14240     MarkTargetSquares(1);
14241
14242     if (gameMode == PlayFromGameFile && !pausing)
14243       PauseEvent();
14244
14245     if (gameMode == IcsExamining && pausing)
14246       limit = pauseExamForwardMostMove;
14247     else
14248       limit = forwardMostMove;
14249
14250     if (target > limit) target = limit;
14251
14252     if (target > 0 && moveList[target - 1][0]) {
14253         int fromX, fromY, toX, toY;
14254         toX = moveList[target - 1][2] - AAA;
14255         toY = moveList[target - 1][3] - ONE;
14256         if (moveList[target - 1][1] == '@') {
14257             if (appData.highlightLastMove) {
14258                 SetHighlights(-1, -1, toX, toY);
14259             }
14260         } else {
14261             fromX = moveList[target - 1][0] - AAA;
14262             fromY = moveList[target - 1][1] - ONE;
14263             if (target == currentMove + 1) {
14264                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14265             }
14266             if (appData.highlightLastMove) {
14267                 SetHighlights(fromX, fromY, toX, toY);
14268             }
14269         }
14270     }
14271     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14272         gameMode == Training || gameMode == PlayFromGameFile ||
14273         gameMode == AnalyzeFile) {
14274         while (currentMove < target) {
14275             SendMoveToProgram(currentMove++, &first);
14276         }
14277     } else {
14278         currentMove = target;
14279     }
14280
14281     if (gameMode == EditGame || gameMode == EndOfGame) {
14282         whiteTimeRemaining = timeRemaining[0][currentMove];
14283         blackTimeRemaining = timeRemaining[1][currentMove];
14284     }
14285     DisplayBothClocks();
14286     DisplayMove(currentMove - 1);
14287     DrawPosition(FALSE, boards[currentMove]);
14288     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14289     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14290         DisplayComment(currentMove - 1, commentList[currentMove]);
14291     }
14292 }
14293
14294
14295 void
14296 ForwardEvent()
14297 {
14298     if (gameMode == IcsExamining && !pausing) {
14299         SendToICS(ics_prefix);
14300         SendToICS("forward\n");
14301     } else {
14302         ForwardInner(currentMove + 1);
14303     }
14304 }
14305
14306 void
14307 ToEndEvent()
14308 {
14309     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14310         /* to optimze, we temporarily turn off analysis mode while we feed
14311          * the remaining moves to the engine. Otherwise we get analysis output
14312          * after each move.
14313          */
14314         if (first.analysisSupport) {
14315           SendToProgram("exit\nforce\n", &first);
14316           first.analyzing = FALSE;
14317         }
14318     }
14319
14320     if (gameMode == IcsExamining && !pausing) {
14321         SendToICS(ics_prefix);
14322         SendToICS("forward 999999\n");
14323     } else {
14324         ForwardInner(forwardMostMove);
14325     }
14326
14327     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14328         /* we have fed all the moves, so reactivate analysis mode */
14329         SendToProgram("analyze\n", &first);
14330         first.analyzing = TRUE;
14331         /*first.maybeThinking = TRUE;*/
14332         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14333     }
14334 }
14335
14336 void
14337 BackwardInner(target)
14338      int target;
14339 {
14340     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14341
14342     if (appData.debugMode)
14343         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14344                 target, currentMove, forwardMostMove);
14345
14346     if (gameMode == EditPosition) return;
14347     MarkTargetSquares(1);
14348     if (currentMove <= backwardMostMove) {
14349         ClearHighlights();
14350         DrawPosition(full_redraw, boards[currentMove]);
14351         return;
14352     }
14353     if (gameMode == PlayFromGameFile && !pausing)
14354       PauseEvent();
14355
14356     if (moveList[target][0]) {
14357         int fromX, fromY, toX, toY;
14358         toX = moveList[target][2] - AAA;
14359         toY = moveList[target][3] - ONE;
14360         if (moveList[target][1] == '@') {
14361             if (appData.highlightLastMove) {
14362                 SetHighlights(-1, -1, toX, toY);
14363             }
14364         } else {
14365             fromX = moveList[target][0] - AAA;
14366             fromY = moveList[target][1] - ONE;
14367             if (target == currentMove - 1) {
14368                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14369             }
14370             if (appData.highlightLastMove) {
14371                 SetHighlights(fromX, fromY, toX, toY);
14372             }
14373         }
14374     }
14375     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14376         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14377         while (currentMove > target) {
14378             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14379                 // null move cannot be undone. Reload program with move history before it.
14380                 int i;
14381                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14382                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14383                 }
14384                 SendBoard(&first, i); 
14385                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14386                 break;
14387             }
14388             SendToProgram("undo\n", &first);
14389             currentMove--;
14390         }
14391     } else {
14392         currentMove = target;
14393     }
14394
14395     if (gameMode == EditGame || gameMode == EndOfGame) {
14396         whiteTimeRemaining = timeRemaining[0][currentMove];
14397         blackTimeRemaining = timeRemaining[1][currentMove];
14398     }
14399     DisplayBothClocks();
14400     DisplayMove(currentMove - 1);
14401     DrawPosition(full_redraw, boards[currentMove]);
14402     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14403     // [HGM] PV info: routine tests if comment empty
14404     DisplayComment(currentMove - 1, commentList[currentMove]);
14405 }
14406
14407 void
14408 BackwardEvent()
14409 {
14410     if (gameMode == IcsExamining && !pausing) {
14411         SendToICS(ics_prefix);
14412         SendToICS("backward\n");
14413     } else {
14414         BackwardInner(currentMove - 1);
14415     }
14416 }
14417
14418 void
14419 ToStartEvent()
14420 {
14421     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14422         /* to optimize, we temporarily turn off analysis mode while we undo
14423          * all the moves. Otherwise we get analysis output after each undo.
14424          */
14425         if (first.analysisSupport) {
14426           SendToProgram("exit\nforce\n", &first);
14427           first.analyzing = FALSE;
14428         }
14429     }
14430
14431     if (gameMode == IcsExamining && !pausing) {
14432         SendToICS(ics_prefix);
14433         SendToICS("backward 999999\n");
14434     } else {
14435         BackwardInner(backwardMostMove);
14436     }
14437
14438     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14439         /* we have fed all the moves, so reactivate analysis mode */
14440         SendToProgram("analyze\n", &first);
14441         first.analyzing = TRUE;
14442         /*first.maybeThinking = TRUE;*/
14443         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14444     }
14445 }
14446
14447 void
14448 ToNrEvent(int to)
14449 {
14450   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14451   if (to >= forwardMostMove) to = forwardMostMove;
14452   if (to <= backwardMostMove) to = backwardMostMove;
14453   if (to < currentMove) {
14454     BackwardInner(to);
14455   } else {
14456     ForwardInner(to);
14457   }
14458 }
14459
14460 void
14461 RevertEvent(Boolean annotate)
14462 {
14463     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14464         return;
14465     }
14466     if (gameMode != IcsExamining) {
14467         DisplayError(_("You are not examining a game"), 0);
14468         return;
14469     }
14470     if (pausing) {
14471         DisplayError(_("You can't revert while pausing"), 0);
14472         return;
14473     }
14474     SendToICS(ics_prefix);
14475     SendToICS("revert\n");
14476 }
14477
14478 void
14479 RetractMoveEvent()
14480 {
14481     switch (gameMode) {
14482       case MachinePlaysWhite:
14483       case MachinePlaysBlack:
14484         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14485             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14486             return;
14487         }
14488         if (forwardMostMove < 2) return;
14489         currentMove = forwardMostMove = forwardMostMove - 2;
14490         whiteTimeRemaining = timeRemaining[0][currentMove];
14491         blackTimeRemaining = timeRemaining[1][currentMove];
14492         DisplayBothClocks();
14493         DisplayMove(currentMove - 1);
14494         ClearHighlights();/*!! could figure this out*/
14495         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14496         SendToProgram("remove\n", &first);
14497         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14498         break;
14499
14500       case BeginningOfGame:
14501       default:
14502         break;
14503
14504       case IcsPlayingWhite:
14505       case IcsPlayingBlack:
14506         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14507             SendToICS(ics_prefix);
14508             SendToICS("takeback 2\n");
14509         } else {
14510             SendToICS(ics_prefix);
14511             SendToICS("takeback 1\n");
14512         }
14513         break;
14514     }
14515 }
14516
14517 void
14518 MoveNowEvent()
14519 {
14520     ChessProgramState *cps;
14521
14522     switch (gameMode) {
14523       case MachinePlaysWhite:
14524         if (!WhiteOnMove(forwardMostMove)) {
14525             DisplayError(_("It is your turn"), 0);
14526             return;
14527         }
14528         cps = &first;
14529         break;
14530       case MachinePlaysBlack:
14531         if (WhiteOnMove(forwardMostMove)) {
14532             DisplayError(_("It is your turn"), 0);
14533             return;
14534         }
14535         cps = &first;
14536         break;
14537       case TwoMachinesPlay:
14538         if (WhiteOnMove(forwardMostMove) ==
14539             (first.twoMachinesColor[0] == 'w')) {
14540             cps = &first;
14541         } else {
14542             cps = &second;
14543         }
14544         break;
14545       case BeginningOfGame:
14546       default:
14547         return;
14548     }
14549     SendToProgram("?\n", cps);
14550 }
14551
14552 void
14553 TruncateGameEvent()
14554 {
14555     EditGameEvent();
14556     if (gameMode != EditGame) return;
14557     TruncateGame();
14558 }
14559
14560 void
14561 TruncateGame()
14562 {
14563     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14564     if (forwardMostMove > currentMove) {
14565         if (gameInfo.resultDetails != NULL) {
14566             free(gameInfo.resultDetails);
14567             gameInfo.resultDetails = NULL;
14568             gameInfo.result = GameUnfinished;
14569         }
14570         forwardMostMove = currentMove;
14571         HistorySet(parseList, backwardMostMove, forwardMostMove,
14572                    currentMove-1);
14573     }
14574 }
14575
14576 void
14577 HintEvent()
14578 {
14579     if (appData.noChessProgram) return;
14580     switch (gameMode) {
14581       case MachinePlaysWhite:
14582         if (WhiteOnMove(forwardMostMove)) {
14583             DisplayError(_("Wait until your turn"), 0);
14584             return;
14585         }
14586         break;
14587       case BeginningOfGame:
14588       case MachinePlaysBlack:
14589         if (!WhiteOnMove(forwardMostMove)) {
14590             DisplayError(_("Wait until your turn"), 0);
14591             return;
14592         }
14593         break;
14594       default:
14595         DisplayError(_("No hint available"), 0);
14596         return;
14597     }
14598     SendToProgram("hint\n", &first);
14599     hintRequested = TRUE;
14600 }
14601
14602 void
14603 BookEvent()
14604 {
14605     if (appData.noChessProgram) return;
14606     switch (gameMode) {
14607       case MachinePlaysWhite:
14608         if (WhiteOnMove(forwardMostMove)) {
14609             DisplayError(_("Wait until your turn"), 0);
14610             return;
14611         }
14612         break;
14613       case BeginningOfGame:
14614       case MachinePlaysBlack:
14615         if (!WhiteOnMove(forwardMostMove)) {
14616             DisplayError(_("Wait until your turn"), 0);
14617             return;
14618         }
14619         break;
14620       case EditPosition:
14621         EditPositionDone(TRUE);
14622         break;
14623       case TwoMachinesPlay:
14624         return;
14625       default:
14626         break;
14627     }
14628     SendToProgram("bk\n", &first);
14629     bookOutput[0] = NULLCHAR;
14630     bookRequested = TRUE;
14631 }
14632
14633 void
14634 AboutGameEvent()
14635 {
14636     char *tags = PGNTags(&gameInfo);
14637     TagsPopUp(tags, CmailMsg());
14638     free(tags);
14639 }
14640
14641 /* end button procedures */
14642
14643 void
14644 PrintPosition(fp, move)
14645      FILE *fp;
14646      int move;
14647 {
14648     int i, j;
14649
14650     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14651         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14652             char c = PieceToChar(boards[move][i][j]);
14653             fputc(c == 'x' ? '.' : c, fp);
14654             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14655         }
14656     }
14657     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14658       fprintf(fp, "white to play\n");
14659     else
14660       fprintf(fp, "black to play\n");
14661 }
14662
14663 void
14664 PrintOpponents(fp)
14665      FILE *fp;
14666 {
14667     if (gameInfo.white != NULL) {
14668         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14669     } else {
14670         fprintf(fp, "\n");
14671     }
14672 }
14673
14674 /* Find last component of program's own name, using some heuristics */
14675 void
14676 TidyProgramName(prog, host, buf)
14677      char *prog, *host, buf[MSG_SIZ];
14678 {
14679     char *p, *q;
14680     int local = (strcmp(host, "localhost") == 0);
14681     while (!local && (p = strchr(prog, ';')) != NULL) {
14682         p++;
14683         while (*p == ' ') p++;
14684         prog = p;
14685     }
14686     if (*prog == '"' || *prog == '\'') {
14687         q = strchr(prog + 1, *prog);
14688     } else {
14689         q = strchr(prog, ' ');
14690     }
14691     if (q == NULL) q = prog + strlen(prog);
14692     p = q;
14693     while (p >= prog && *p != '/' && *p != '\\') p--;
14694     p++;
14695     if(p == prog && *p == '"') p++;
14696     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14697     memcpy(buf, p, q - p);
14698     buf[q - p] = NULLCHAR;
14699     if (!local) {
14700         strcat(buf, "@");
14701         strcat(buf, host);
14702     }
14703 }
14704
14705 char *
14706 TimeControlTagValue()
14707 {
14708     char buf[MSG_SIZ];
14709     if (!appData.clockMode) {
14710       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14711     } else if (movesPerSession > 0) {
14712       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14713     } else if (timeIncrement == 0) {
14714       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14715     } else {
14716       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14717     }
14718     return StrSave(buf);
14719 }
14720
14721 void
14722 SetGameInfo()
14723 {
14724     /* This routine is used only for certain modes */
14725     VariantClass v = gameInfo.variant;
14726     ChessMove r = GameUnfinished;
14727     char *p = NULL;
14728
14729     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14730         r = gameInfo.result;
14731         p = gameInfo.resultDetails;
14732         gameInfo.resultDetails = NULL;
14733     }
14734     ClearGameInfo(&gameInfo);
14735     gameInfo.variant = v;
14736
14737     switch (gameMode) {
14738       case MachinePlaysWhite:
14739         gameInfo.event = StrSave( appData.pgnEventHeader );
14740         gameInfo.site = StrSave(HostName());
14741         gameInfo.date = PGNDate();
14742         gameInfo.round = StrSave("-");
14743         gameInfo.white = StrSave(first.tidy);
14744         gameInfo.black = StrSave(UserName());
14745         gameInfo.timeControl = TimeControlTagValue();
14746         break;
14747
14748       case MachinePlaysBlack:
14749         gameInfo.event = StrSave( appData.pgnEventHeader );
14750         gameInfo.site = StrSave(HostName());
14751         gameInfo.date = PGNDate();
14752         gameInfo.round = StrSave("-");
14753         gameInfo.white = StrSave(UserName());
14754         gameInfo.black = StrSave(first.tidy);
14755         gameInfo.timeControl = TimeControlTagValue();
14756         break;
14757
14758       case TwoMachinesPlay:
14759         gameInfo.event = StrSave( appData.pgnEventHeader );
14760         gameInfo.site = StrSave(HostName());
14761         gameInfo.date = PGNDate();
14762         if (roundNr > 0) {
14763             char buf[MSG_SIZ];
14764             snprintf(buf, MSG_SIZ, "%d", roundNr);
14765             gameInfo.round = StrSave(buf);
14766         } else {
14767             gameInfo.round = StrSave("-");
14768         }
14769         if (first.twoMachinesColor[0] == 'w') {
14770             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14771             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14772         } else {
14773             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14774             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14775         }
14776         gameInfo.timeControl = TimeControlTagValue();
14777         break;
14778
14779       case EditGame:
14780         gameInfo.event = StrSave("Edited game");
14781         gameInfo.site = StrSave(HostName());
14782         gameInfo.date = PGNDate();
14783         gameInfo.round = StrSave("-");
14784         gameInfo.white = StrSave("-");
14785         gameInfo.black = StrSave("-");
14786         gameInfo.result = r;
14787         gameInfo.resultDetails = p;
14788         break;
14789
14790       case EditPosition:
14791         gameInfo.event = StrSave("Edited position");
14792         gameInfo.site = StrSave(HostName());
14793         gameInfo.date = PGNDate();
14794         gameInfo.round = StrSave("-");
14795         gameInfo.white = StrSave("-");
14796         gameInfo.black = StrSave("-");
14797         break;
14798
14799       case IcsPlayingWhite:
14800       case IcsPlayingBlack:
14801       case IcsObserving:
14802       case IcsExamining:
14803         break;
14804
14805       case PlayFromGameFile:
14806         gameInfo.event = StrSave("Game from non-PGN file");
14807         gameInfo.site = StrSave(HostName());
14808         gameInfo.date = PGNDate();
14809         gameInfo.round = StrSave("-");
14810         gameInfo.white = StrSave("?");
14811         gameInfo.black = StrSave("?");
14812         break;
14813
14814       default:
14815         break;
14816     }
14817 }
14818
14819 void
14820 ReplaceComment(index, text)
14821      int index;
14822      char *text;
14823 {
14824     int len;
14825     char *p;
14826     float score;
14827
14828     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14829        pvInfoList[index-1].depth == len &&
14830        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14831        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14832     while (*text == '\n') text++;
14833     len = strlen(text);
14834     while (len > 0 && text[len - 1] == '\n') len--;
14835
14836     if (commentList[index] != NULL)
14837       free(commentList[index]);
14838
14839     if (len == 0) {
14840         commentList[index] = NULL;
14841         return;
14842     }
14843   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14844       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14845       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14846     commentList[index] = (char *) malloc(len + 2);
14847     strncpy(commentList[index], text, len);
14848     commentList[index][len] = '\n';
14849     commentList[index][len + 1] = NULLCHAR;
14850   } else {
14851     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14852     char *p;
14853     commentList[index] = (char *) malloc(len + 7);
14854     safeStrCpy(commentList[index], "{\n", 3);
14855     safeStrCpy(commentList[index]+2, text, len+1);
14856     commentList[index][len+2] = NULLCHAR;
14857     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14858     strcat(commentList[index], "\n}\n");
14859   }
14860 }
14861
14862 void
14863 CrushCRs(text)
14864      char *text;
14865 {
14866   char *p = text;
14867   char *q = text;
14868   char ch;
14869
14870   do {
14871     ch = *p++;
14872     if (ch == '\r') continue;
14873     *q++ = ch;
14874   } while (ch != '\0');
14875 }
14876
14877 void
14878 AppendComment(index, text, addBraces)
14879      int index;
14880      char *text;
14881      Boolean addBraces; // [HGM] braces: tells if we should add {}
14882 {
14883     int oldlen, len;
14884     char *old;
14885
14886 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14887     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14888
14889     CrushCRs(text);
14890     while (*text == '\n') text++;
14891     len = strlen(text);
14892     while (len > 0 && text[len - 1] == '\n') len--;
14893
14894     if (len == 0) return;
14895
14896     if (commentList[index] != NULL) {
14897       Boolean addClosingBrace = addBraces;
14898         old = commentList[index];
14899         oldlen = strlen(old);
14900         while(commentList[index][oldlen-1] ==  '\n')
14901           commentList[index][--oldlen] = NULLCHAR;
14902         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14903         safeStrCpy(commentList[index], old, oldlen + len + 6);
14904         free(old);
14905         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14906         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14907           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14908           while (*text == '\n') { text++; len--; }
14909           commentList[index][--oldlen] = NULLCHAR;
14910       }
14911         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14912         else          strcat(commentList[index], "\n");
14913         strcat(commentList[index], text);
14914         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14915         else          strcat(commentList[index], "\n");
14916     } else {
14917         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14918         if(addBraces)
14919           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14920         else commentList[index][0] = NULLCHAR;
14921         strcat(commentList[index], text);
14922         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14923         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14924     }
14925 }
14926
14927 static char * FindStr( char * text, char * sub_text )
14928 {
14929     char * result = strstr( text, sub_text );
14930
14931     if( result != NULL ) {
14932         result += strlen( sub_text );
14933     }
14934
14935     return result;
14936 }
14937
14938 /* [AS] Try to extract PV info from PGN comment */
14939 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14940 char *GetInfoFromComment( int index, char * text )
14941 {
14942     char * sep = text, *p;
14943
14944     if( text != NULL && index > 0 ) {
14945         int score = 0;
14946         int depth = 0;
14947         int time = -1, sec = 0, deci;
14948         char * s_eval = FindStr( text, "[%eval " );
14949         char * s_emt = FindStr( text, "[%emt " );
14950
14951         if( s_eval != NULL || s_emt != NULL ) {
14952             /* New style */
14953             char delim;
14954
14955             if( s_eval != NULL ) {
14956                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14957                     return text;
14958                 }
14959
14960                 if( delim != ']' ) {
14961                     return text;
14962                 }
14963             }
14964
14965             if( s_emt != NULL ) {
14966             }
14967                 return text;
14968         }
14969         else {
14970             /* We expect something like: [+|-]nnn.nn/dd */
14971             int score_lo = 0;
14972
14973             if(*text != '{') return text; // [HGM] braces: must be normal comment
14974
14975             sep = strchr( text, '/' );
14976             if( sep == NULL || sep < (text+4) ) {
14977                 return text;
14978             }
14979
14980             p = text;
14981             if(p[1] == '(') { // comment starts with PV
14982                p = strchr(p, ')'); // locate end of PV
14983                if(p == NULL || sep < p+5) return text;
14984                // at this point we have something like "{(.*) +0.23/6 ..."
14985                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14986                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14987                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14988             }
14989             time = -1; sec = -1; deci = -1;
14990             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14991                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14992                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14993                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14994                 return text;
14995             }
14996
14997             if( score_lo < 0 || score_lo >= 100 ) {
14998                 return text;
14999             }
15000
15001             if(sec >= 0) time = 600*time + 10*sec; else
15002             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15003
15004             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15005
15006             /* [HGM] PV time: now locate end of PV info */
15007             while( *++sep >= '0' && *sep <= '9'); // strip depth
15008             if(time >= 0)
15009             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15010             if(sec >= 0)
15011             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15012             if(deci >= 0)
15013             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15014             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15015         }
15016
15017         if( depth <= 0 ) {
15018             return text;
15019         }
15020
15021         if( time < 0 ) {
15022             time = -1;
15023         }
15024
15025         pvInfoList[index-1].depth = depth;
15026         pvInfoList[index-1].score = score;
15027         pvInfoList[index-1].time  = 10*time; // centi-sec
15028         if(*sep == '}') *sep = 0; else *--sep = '{';
15029         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15030     }
15031     return sep;
15032 }
15033
15034 void
15035 SendToProgram(message, cps)
15036      char *message;
15037      ChessProgramState *cps;
15038 {
15039     int count, outCount, error;
15040     char buf[MSG_SIZ];
15041
15042     if (cps->pr == NoProc) return;
15043     Attention(cps);
15044
15045     if (appData.debugMode) {
15046         TimeMark now;
15047         GetTimeMark(&now);
15048         fprintf(debugFP, "%ld >%-6s: %s",
15049                 SubtractTimeMarks(&now, &programStartTime),
15050                 cps->which, message);
15051     }
15052
15053     count = strlen(message);
15054     outCount = OutputToProcess(cps->pr, message, count, &error);
15055     if (outCount < count && !exiting
15056                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15057       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15058       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15059         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15060             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15061                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15062                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15063                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15064             } else {
15065                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15066                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15067                 gameInfo.result = res;
15068             }
15069             gameInfo.resultDetails = StrSave(buf);
15070         }
15071         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15072         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15073     }
15074 }
15075
15076 void
15077 ReceiveFromProgram(isr, closure, message, count, error)
15078      InputSourceRef isr;
15079      VOIDSTAR closure;
15080      char *message;
15081      int count;
15082      int error;
15083 {
15084     char *end_str;
15085     char buf[MSG_SIZ];
15086     ChessProgramState *cps = (ChessProgramState *)closure;
15087
15088     if (isr != cps->isr) return; /* Killed intentionally */
15089     if (count <= 0) {
15090         if (count == 0) {
15091             RemoveInputSource(cps->isr);
15092             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15093             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15094                     _(cps->which), cps->program);
15095         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15096                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15097                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15098                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15099                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15100                 } else {
15101                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15102                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15103                     gameInfo.result = res;
15104                 }
15105                 gameInfo.resultDetails = StrSave(buf);
15106             }
15107             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15108             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15109         } else {
15110             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15111                     _(cps->which), cps->program);
15112             RemoveInputSource(cps->isr);
15113
15114             /* [AS] Program is misbehaving badly... kill it */
15115             if( count == -2 ) {
15116                 DestroyChildProcess( cps->pr, 9 );
15117                 cps->pr = NoProc;
15118             }
15119
15120             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15121         }
15122         return;
15123     }
15124
15125     if ((end_str = strchr(message, '\r')) != NULL)
15126       *end_str = NULLCHAR;
15127     if ((end_str = strchr(message, '\n')) != NULL)
15128       *end_str = NULLCHAR;
15129
15130     if (appData.debugMode) {
15131         TimeMark now; int print = 1;
15132         char *quote = ""; char c; int i;
15133
15134         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15135                 char start = message[0];
15136                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15137                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15138                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15139                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15140                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15141                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15142                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15143                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15144                    sscanf(message, "hint: %c", &c)!=1 && 
15145                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15146                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15147                     print = (appData.engineComments >= 2);
15148                 }
15149                 message[0] = start; // restore original message
15150         }
15151         if(print) {
15152                 GetTimeMark(&now);
15153                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15154                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15155                         quote,
15156                         message);
15157         }
15158     }
15159
15160     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15161     if (appData.icsEngineAnalyze) {
15162         if (strstr(message, "whisper") != NULL ||
15163              strstr(message, "kibitz") != NULL ||
15164             strstr(message, "tellics") != NULL) return;
15165     }
15166
15167     HandleMachineMove(message, cps);
15168 }
15169
15170
15171 void
15172 SendTimeControl(cps, mps, tc, inc, sd, st)
15173      ChessProgramState *cps;
15174      int mps, inc, sd, st;
15175      long tc;
15176 {
15177     char buf[MSG_SIZ];
15178     int seconds;
15179
15180     if( timeControl_2 > 0 ) {
15181         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15182             tc = timeControl_2;
15183         }
15184     }
15185     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15186     inc /= cps->timeOdds;
15187     st  /= cps->timeOdds;
15188
15189     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15190
15191     if (st > 0) {
15192       /* Set exact time per move, normally using st command */
15193       if (cps->stKludge) {
15194         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15195         seconds = st % 60;
15196         if (seconds == 0) {
15197           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15198         } else {
15199           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15200         }
15201       } else {
15202         snprintf(buf, MSG_SIZ, "st %d\n", st);
15203       }
15204     } else {
15205       /* Set conventional or incremental time control, using level command */
15206       if (seconds == 0) {
15207         /* Note old gnuchess bug -- minutes:seconds used to not work.
15208            Fixed in later versions, but still avoid :seconds
15209            when seconds is 0. */
15210         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15211       } else {
15212         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15213                  seconds, inc/1000.);
15214       }
15215     }
15216     SendToProgram(buf, cps);
15217
15218     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15219     /* Orthogonally, limit search to given depth */
15220     if (sd > 0) {
15221       if (cps->sdKludge) {
15222         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15223       } else {
15224         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15225       }
15226       SendToProgram(buf, cps);
15227     }
15228
15229     if(cps->nps >= 0) { /* [HGM] nps */
15230         if(cps->supportsNPS == FALSE)
15231           cps->nps = -1; // don't use if engine explicitly says not supported!
15232         else {
15233           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15234           SendToProgram(buf, cps);
15235         }
15236     }
15237 }
15238
15239 ChessProgramState *WhitePlayer()
15240 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15241 {
15242     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15243        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15244         return &second;
15245     return &first;
15246 }
15247
15248 void
15249 SendTimeRemaining(cps, machineWhite)
15250      ChessProgramState *cps;
15251      int /*boolean*/ machineWhite;
15252 {
15253     char message[MSG_SIZ];
15254     long time, otime;
15255
15256     /* Note: this routine must be called when the clocks are stopped
15257        or when they have *just* been set or switched; otherwise
15258        it will be off by the time since the current tick started.
15259     */
15260     if (machineWhite) {
15261         time = whiteTimeRemaining / 10;
15262         otime = blackTimeRemaining / 10;
15263     } else {
15264         time = blackTimeRemaining / 10;
15265         otime = whiteTimeRemaining / 10;
15266     }
15267     /* [HGM] translate opponent's time by time-odds factor */
15268     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15269     if (appData.debugMode) {
15270         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15271     }
15272
15273     if (time <= 0) time = 1;
15274     if (otime <= 0) otime = 1;
15275
15276     snprintf(message, MSG_SIZ, "time %ld\n", time);
15277     SendToProgram(message, cps);
15278
15279     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15280     SendToProgram(message, cps);
15281 }
15282
15283 int
15284 BoolFeature(p, name, loc, cps)
15285      char **p;
15286      char *name;
15287      int *loc;
15288      ChessProgramState *cps;
15289 {
15290   char buf[MSG_SIZ];
15291   int len = strlen(name);
15292   int val;
15293
15294   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15295     (*p) += len + 1;
15296     sscanf(*p, "%d", &val);
15297     *loc = (val != 0);
15298     while (**p && **p != ' ')
15299       (*p)++;
15300     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15301     SendToProgram(buf, cps);
15302     return TRUE;
15303   }
15304   return FALSE;
15305 }
15306
15307 int
15308 IntFeature(p, name, loc, cps)
15309      char **p;
15310      char *name;
15311      int *loc;
15312      ChessProgramState *cps;
15313 {
15314   char buf[MSG_SIZ];
15315   int len = strlen(name);
15316   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15317     (*p) += len + 1;
15318     sscanf(*p, "%d", loc);
15319     while (**p && **p != ' ') (*p)++;
15320     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15321     SendToProgram(buf, cps);
15322     return TRUE;
15323   }
15324   return FALSE;
15325 }
15326
15327 int
15328 StringFeature(p, name, loc, cps)
15329      char **p;
15330      char *name;
15331      char loc[];
15332      ChessProgramState *cps;
15333 {
15334   char buf[MSG_SIZ];
15335   int len = strlen(name);
15336   if (strncmp((*p), name, len) == 0
15337       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15338     (*p) += len + 2;
15339     sscanf(*p, "%[^\"]", loc);
15340     while (**p && **p != '\"') (*p)++;
15341     if (**p == '\"') (*p)++;
15342     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15343     SendToProgram(buf, cps);
15344     return TRUE;
15345   }
15346   return FALSE;
15347 }
15348
15349 int
15350 ParseOption(Option *opt, ChessProgramState *cps)
15351 // [HGM] options: process the string that defines an engine option, and determine
15352 // name, type, default value, and allowed value range
15353 {
15354         char *p, *q, buf[MSG_SIZ];
15355         int n, min = (-1)<<31, max = 1<<31, def;
15356
15357         if(p = strstr(opt->name, " -spin ")) {
15358             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15359             if(max < min) max = min; // enforce consistency
15360             if(def < min) def = min;
15361             if(def > max) def = max;
15362             opt->value = def;
15363             opt->min = min;
15364             opt->max = max;
15365             opt->type = Spin;
15366         } else if((p = strstr(opt->name, " -slider "))) {
15367             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15368             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15369             if(max < min) max = min; // enforce consistency
15370             if(def < min) def = min;
15371             if(def > max) def = max;
15372             opt->value = def;
15373             opt->min = min;
15374             opt->max = max;
15375             opt->type = Spin; // Slider;
15376         } else if((p = strstr(opt->name, " -string "))) {
15377             opt->textValue = p+9;
15378             opt->type = TextBox;
15379         } else if((p = strstr(opt->name, " -file "))) {
15380             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15381             opt->textValue = p+7;
15382             opt->type = FileName; // FileName;
15383         } else if((p = strstr(opt->name, " -path "))) {
15384             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15385             opt->textValue = p+7;
15386             opt->type = PathName; // PathName;
15387         } else if(p = strstr(opt->name, " -check ")) {
15388             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15389             opt->value = (def != 0);
15390             opt->type = CheckBox;
15391         } else if(p = strstr(opt->name, " -combo ")) {
15392             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15393             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15394             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15395             opt->value = n = 0;
15396             while(q = StrStr(q, " /// ")) {
15397                 n++; *q = 0;    // count choices, and null-terminate each of them
15398                 q += 5;
15399                 if(*q == '*') { // remember default, which is marked with * prefix
15400                     q++;
15401                     opt->value = n;
15402                 }
15403                 cps->comboList[cps->comboCnt++] = q;
15404             }
15405             cps->comboList[cps->comboCnt++] = NULL;
15406             opt->max = n + 1;
15407             opt->type = ComboBox;
15408         } else if(p = strstr(opt->name, " -button")) {
15409             opt->type = Button;
15410         } else if(p = strstr(opt->name, " -save")) {
15411             opt->type = SaveButton;
15412         } else return FALSE;
15413         *p = 0; // terminate option name
15414         // now look if the command-line options define a setting for this engine option.
15415         if(cps->optionSettings && cps->optionSettings[0])
15416             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15417         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15418           snprintf(buf, MSG_SIZ, "option %s", p);
15419                 if(p = strstr(buf, ",")) *p = 0;
15420                 if(q = strchr(buf, '=')) switch(opt->type) {
15421                     case ComboBox:
15422                         for(n=0; n<opt->max; n++)
15423                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15424                         break;
15425                     case TextBox:
15426                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15427                         break;
15428                     case Spin:
15429                     case CheckBox:
15430                         opt->value = atoi(q+1);
15431                     default:
15432                         break;
15433                 }
15434                 strcat(buf, "\n");
15435                 SendToProgram(buf, cps);
15436         }
15437         return TRUE;
15438 }
15439
15440 void
15441 FeatureDone(cps, val)
15442      ChessProgramState* cps;
15443      int val;
15444 {
15445   DelayedEventCallback cb = GetDelayedEvent();
15446   if ((cb == InitBackEnd3 && cps == &first) ||
15447       (cb == SettingsMenuIfReady && cps == &second) ||
15448       (cb == LoadEngine) ||
15449       (cb == TwoMachinesEventIfReady)) {
15450     CancelDelayedEvent();
15451     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15452   }
15453   cps->initDone = val;
15454 }
15455
15456 /* Parse feature command from engine */
15457 void
15458 ParseFeatures(args, cps)
15459      char* args;
15460      ChessProgramState *cps;
15461 {
15462   char *p = args;
15463   char *q;
15464   int val;
15465   char buf[MSG_SIZ];
15466
15467   for (;;) {
15468     while (*p == ' ') p++;
15469     if (*p == NULLCHAR) return;
15470
15471     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15472     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15473     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15474     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15475     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15476     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15477     if (BoolFeature(&p, "reuse", &val, cps)) {
15478       /* Engine can disable reuse, but can't enable it if user said no */
15479       if (!val) cps->reuse = FALSE;
15480       continue;
15481     }
15482     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15483     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
15484       if (gameMode == TwoMachinesPlay) {
15485         DisplayTwoMachinesTitle();
15486       } else {
15487         DisplayTitle("");
15488       }
15489       continue;
15490     }
15491     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
15492     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15493     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15494     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15495     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15496     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15497     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15498     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15499     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15500     if (IntFeature(&p, "done", &val, cps)) {
15501       FeatureDone(cps, val);
15502       continue;
15503     }
15504     /* Added by Tord: */
15505     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15506     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15507     /* End of additions by Tord */
15508
15509     /* [HGM] added features: */
15510     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15511     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15512     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15513     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15514     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15515     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
15516     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
15517         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15518           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15519             SendToProgram(buf, cps);
15520             continue;
15521         }
15522         if(cps->nrOptions >= MAX_OPTIONS) {
15523             cps->nrOptions--;
15524             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15525             DisplayError(buf, 0);
15526         }
15527         continue;
15528     }
15529     /* End of additions by HGM */
15530
15531     /* unknown feature: complain and skip */
15532     q = p;
15533     while (*q && *q != '=') q++;
15534     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15535     SendToProgram(buf, cps);
15536     p = q;
15537     if (*p == '=') {
15538       p++;
15539       if (*p == '\"') {
15540         p++;
15541         while (*p && *p != '\"') p++;
15542         if (*p == '\"') p++;
15543       } else {
15544         while (*p && *p != ' ') p++;
15545       }
15546     }
15547   }
15548
15549 }
15550
15551 void
15552 PeriodicUpdatesEvent(newState)
15553      int newState;
15554 {
15555     if (newState == appData.periodicUpdates)
15556       return;
15557
15558     appData.periodicUpdates=newState;
15559
15560     /* Display type changes, so update it now */
15561 //    DisplayAnalysis();
15562
15563     /* Get the ball rolling again... */
15564     if (newState) {
15565         AnalysisPeriodicEvent(1);
15566         StartAnalysisClock();
15567     }
15568 }
15569
15570 void
15571 PonderNextMoveEvent(newState)
15572      int newState;
15573 {
15574     if (newState == appData.ponderNextMove) return;
15575     if (gameMode == EditPosition) EditPositionDone(TRUE);
15576     if (newState) {
15577         SendToProgram("hard\n", &first);
15578         if (gameMode == TwoMachinesPlay) {
15579             SendToProgram("hard\n", &second);
15580         }
15581     } else {
15582         SendToProgram("easy\n", &first);
15583         thinkOutput[0] = NULLCHAR;
15584         if (gameMode == TwoMachinesPlay) {
15585             SendToProgram("easy\n", &second);
15586         }
15587     }
15588     appData.ponderNextMove = newState;
15589 }
15590
15591 void
15592 NewSettingEvent(option, feature, command, value)
15593      char *command;
15594      int option, value, *feature;
15595 {
15596     char buf[MSG_SIZ];
15597
15598     if (gameMode == EditPosition) EditPositionDone(TRUE);
15599     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15600     if(feature == NULL || *feature) SendToProgram(buf, &first);
15601     if (gameMode == TwoMachinesPlay) {
15602         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15603     }
15604 }
15605
15606 void
15607 ShowThinkingEvent()
15608 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15609 {
15610     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15611     int newState = appData.showThinking
15612         // [HGM] thinking: other features now need thinking output as well
15613         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15614
15615     if (oldState == newState) return;
15616     oldState = newState;
15617     if (gameMode == EditPosition) EditPositionDone(TRUE);
15618     if (oldState) {
15619         SendToProgram("post\n", &first);
15620         if (gameMode == TwoMachinesPlay) {
15621             SendToProgram("post\n", &second);
15622         }
15623     } else {
15624         SendToProgram("nopost\n", &first);
15625         thinkOutput[0] = NULLCHAR;
15626         if (gameMode == TwoMachinesPlay) {
15627             SendToProgram("nopost\n", &second);
15628         }
15629     }
15630 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15631 }
15632
15633 void
15634 AskQuestionEvent(title, question, replyPrefix, which)
15635      char *title; char *question; char *replyPrefix; char *which;
15636 {
15637   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15638   if (pr == NoProc) return;
15639   AskQuestion(title, question, replyPrefix, pr);
15640 }
15641
15642 void
15643 TypeInEvent(char firstChar)
15644 {
15645     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15646         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15647         gameMode == AnalyzeMode || gameMode == EditGame || 
15648         gameMode == EditPosition || gameMode == IcsExamining ||
15649         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15650         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15651                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15652                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15653         gameMode == Training) PopUpMoveDialog(firstChar);
15654 }
15655
15656 void
15657 TypeInDoneEvent(char *move)
15658 {
15659         Board board;
15660         int n, fromX, fromY, toX, toY;
15661         char promoChar;
15662         ChessMove moveType;
15663
15664         // [HGM] FENedit
15665         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15666                 EditPositionPasteFEN(move);
15667                 return;
15668         }
15669         // [HGM] movenum: allow move number to be typed in any mode
15670         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15671           ToNrEvent(2*n-1);
15672           return;
15673         }
15674
15675       if (gameMode != EditGame && currentMove != forwardMostMove && 
15676         gameMode != Training) {
15677         DisplayMoveError(_("Displayed move is not current"));
15678       } else {
15679         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15680           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15681         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15682         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15683           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15684           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15685         } else {
15686           DisplayMoveError(_("Could not parse move"));
15687         }
15688       }
15689 }
15690
15691 void
15692 DisplayMove(moveNumber)
15693      int moveNumber;
15694 {
15695     char message[MSG_SIZ];
15696     char res[MSG_SIZ];
15697     char cpThinkOutput[MSG_SIZ];
15698
15699     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15700
15701     if (moveNumber == forwardMostMove - 1 ||
15702         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15703
15704         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15705
15706         if (strchr(cpThinkOutput, '\n')) {
15707             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15708         }
15709     } else {
15710         *cpThinkOutput = NULLCHAR;
15711     }
15712
15713     /* [AS] Hide thinking from human user */
15714     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15715         *cpThinkOutput = NULLCHAR;
15716         if( thinkOutput[0] != NULLCHAR ) {
15717             int i;
15718
15719             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15720                 cpThinkOutput[i] = '.';
15721             }
15722             cpThinkOutput[i] = NULLCHAR;
15723             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15724         }
15725     }
15726
15727     if (moveNumber == forwardMostMove - 1 &&
15728         gameInfo.resultDetails != NULL) {
15729         if (gameInfo.resultDetails[0] == NULLCHAR) {
15730           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15731         } else {
15732           snprintf(res, MSG_SIZ, " {%s} %s",
15733                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15734         }
15735     } else {
15736         res[0] = NULLCHAR;
15737     }
15738
15739     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15740         DisplayMessage(res, cpThinkOutput);
15741     } else {
15742       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15743                 WhiteOnMove(moveNumber) ? " " : ".. ",
15744                 parseList[moveNumber], res);
15745         DisplayMessage(message, cpThinkOutput);
15746     }
15747 }
15748
15749 void
15750 DisplayComment(moveNumber, text)
15751      int moveNumber;
15752      char *text;
15753 {
15754     char title[MSG_SIZ];
15755
15756     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15757       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15758     } else {
15759       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15760               WhiteOnMove(moveNumber) ? " " : ".. ",
15761               parseList[moveNumber]);
15762     }
15763     if (text != NULL && (appData.autoDisplayComment || commentUp))
15764         CommentPopUp(title, text);
15765 }
15766
15767 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15768  * might be busy thinking or pondering.  It can be omitted if your
15769  * gnuchess is configured to stop thinking immediately on any user
15770  * input.  However, that gnuchess feature depends on the FIONREAD
15771  * ioctl, which does not work properly on some flavors of Unix.
15772  */
15773 void
15774 Attention(cps)
15775      ChessProgramState *cps;
15776 {
15777 #if ATTENTION
15778     if (!cps->useSigint) return;
15779     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15780     switch (gameMode) {
15781       case MachinePlaysWhite:
15782       case MachinePlaysBlack:
15783       case TwoMachinesPlay:
15784       case IcsPlayingWhite:
15785       case IcsPlayingBlack:
15786       case AnalyzeMode:
15787       case AnalyzeFile:
15788         /* Skip if we know it isn't thinking */
15789         if (!cps->maybeThinking) return;
15790         if (appData.debugMode)
15791           fprintf(debugFP, "Interrupting %s\n", cps->which);
15792         InterruptChildProcess(cps->pr);
15793         cps->maybeThinking = FALSE;
15794         break;
15795       default:
15796         break;
15797     }
15798 #endif /*ATTENTION*/
15799 }
15800
15801 int
15802 CheckFlags()
15803 {
15804     if (whiteTimeRemaining <= 0) {
15805         if (!whiteFlag) {
15806             whiteFlag = TRUE;
15807             if (appData.icsActive) {
15808                 if (appData.autoCallFlag &&
15809                     gameMode == IcsPlayingBlack && !blackFlag) {
15810                   SendToICS(ics_prefix);
15811                   SendToICS("flag\n");
15812                 }
15813             } else {
15814                 if (blackFlag) {
15815                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15816                 } else {
15817                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15818                     if (appData.autoCallFlag) {
15819                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15820                         return TRUE;
15821                     }
15822                 }
15823             }
15824         }
15825     }
15826     if (blackTimeRemaining <= 0) {
15827         if (!blackFlag) {
15828             blackFlag = TRUE;
15829             if (appData.icsActive) {
15830                 if (appData.autoCallFlag &&
15831                     gameMode == IcsPlayingWhite && !whiteFlag) {
15832                   SendToICS(ics_prefix);
15833                   SendToICS("flag\n");
15834                 }
15835             } else {
15836                 if (whiteFlag) {
15837                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15838                 } else {
15839                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15840                     if (appData.autoCallFlag) {
15841                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15842                         return TRUE;
15843                     }
15844                 }
15845             }
15846         }
15847     }
15848     return FALSE;
15849 }
15850
15851 void
15852 CheckTimeControl()
15853 {
15854     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15855         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15856
15857     /*
15858      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15859      */
15860     if ( !WhiteOnMove(forwardMostMove) ) {
15861         /* White made time control */
15862         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15863         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15864         /* [HGM] time odds: correct new time quota for time odds! */
15865                                             / WhitePlayer()->timeOdds;
15866         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15867     } else {
15868         lastBlack -= blackTimeRemaining;
15869         /* Black made time control */
15870         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15871                                             / WhitePlayer()->other->timeOdds;
15872         lastWhite = whiteTimeRemaining;
15873     }
15874 }
15875
15876 void
15877 DisplayBothClocks()
15878 {
15879     int wom = gameMode == EditPosition ?
15880       !blackPlaysFirst : WhiteOnMove(currentMove);
15881     DisplayWhiteClock(whiteTimeRemaining, wom);
15882     DisplayBlackClock(blackTimeRemaining, !wom);
15883 }
15884
15885
15886 /* Timekeeping seems to be a portability nightmare.  I think everyone
15887    has ftime(), but I'm really not sure, so I'm including some ifdefs
15888    to use other calls if you don't.  Clocks will be less accurate if
15889    you have neither ftime nor gettimeofday.
15890 */
15891
15892 /* VS 2008 requires the #include outside of the function */
15893 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15894 #include <sys/timeb.h>
15895 #endif
15896
15897 /* Get the current time as a TimeMark */
15898 void
15899 GetTimeMark(tm)
15900      TimeMark *tm;
15901 {
15902 #if HAVE_GETTIMEOFDAY
15903
15904     struct timeval timeVal;
15905     struct timezone timeZone;
15906
15907     gettimeofday(&timeVal, &timeZone);
15908     tm->sec = (long) timeVal.tv_sec;
15909     tm->ms = (int) (timeVal.tv_usec / 1000L);
15910
15911 #else /*!HAVE_GETTIMEOFDAY*/
15912 #if HAVE_FTIME
15913
15914 // include <sys/timeb.h> / moved to just above start of function
15915     struct timeb timeB;
15916
15917     ftime(&timeB);
15918     tm->sec = (long) timeB.time;
15919     tm->ms = (int) timeB.millitm;
15920
15921 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15922     tm->sec = (long) time(NULL);
15923     tm->ms = 0;
15924 #endif
15925 #endif
15926 }
15927
15928 /* Return the difference in milliseconds between two
15929    time marks.  We assume the difference will fit in a long!
15930 */
15931 long
15932 SubtractTimeMarks(tm2, tm1)
15933      TimeMark *tm2, *tm1;
15934 {
15935     return 1000L*(tm2->sec - tm1->sec) +
15936            (long) (tm2->ms - tm1->ms);
15937 }
15938
15939
15940 /*
15941  * Code to manage the game clocks.
15942  *
15943  * In tournament play, black starts the clock and then white makes a move.
15944  * We give the human user a slight advantage if he is playing white---the
15945  * clocks don't run until he makes his first move, so it takes zero time.
15946  * Also, we don't account for network lag, so we could get out of sync
15947  * with GNU Chess's clock -- but then, referees are always right.
15948  */
15949
15950 static TimeMark tickStartTM;
15951 static long intendedTickLength;
15952
15953 long
15954 NextTickLength(timeRemaining)
15955      long timeRemaining;
15956 {
15957     long nominalTickLength, nextTickLength;
15958
15959     if (timeRemaining > 0L && timeRemaining <= 10000L)
15960       nominalTickLength = 100L;
15961     else
15962       nominalTickLength = 1000L;
15963     nextTickLength = timeRemaining % nominalTickLength;
15964     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15965
15966     return nextTickLength;
15967 }
15968
15969 /* Adjust clock one minute up or down */
15970 void
15971 AdjustClock(Boolean which, int dir)
15972 {
15973     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15974     if(which) blackTimeRemaining += 60000*dir;
15975     else      whiteTimeRemaining += 60000*dir;
15976     DisplayBothClocks();
15977     adjustedClock = TRUE;
15978 }
15979
15980 /* Stop clocks and reset to a fresh time control */
15981 void
15982 ResetClocks()
15983 {
15984     (void) StopClockTimer();
15985     if (appData.icsActive) {
15986         whiteTimeRemaining = blackTimeRemaining = 0;
15987     } else if (searchTime) {
15988         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15989         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15990     } else { /* [HGM] correct new time quote for time odds */
15991         whiteTC = blackTC = fullTimeControlString;
15992         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15993         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15994     }
15995     if (whiteFlag || blackFlag) {
15996         DisplayTitle("");
15997         whiteFlag = blackFlag = FALSE;
15998     }
15999     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16000     DisplayBothClocks();
16001     adjustedClock = FALSE;
16002 }
16003
16004 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16005
16006 /* Decrement running clock by amount of time that has passed */
16007 void
16008 DecrementClocks()
16009 {
16010     long timeRemaining;
16011     long lastTickLength, fudge;
16012     TimeMark now;
16013
16014     if (!appData.clockMode) return;
16015     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16016
16017     GetTimeMark(&now);
16018
16019     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16020
16021     /* Fudge if we woke up a little too soon */
16022     fudge = intendedTickLength - lastTickLength;
16023     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16024
16025     if (WhiteOnMove(forwardMostMove)) {
16026         if(whiteNPS >= 0) lastTickLength = 0;
16027         timeRemaining = whiteTimeRemaining -= lastTickLength;
16028         if(timeRemaining < 0 && !appData.icsActive) {
16029             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16030             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16031                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16032                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16033             }
16034         }
16035         DisplayWhiteClock(whiteTimeRemaining - fudge,
16036                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16037     } else {
16038         if(blackNPS >= 0) lastTickLength = 0;
16039         timeRemaining = blackTimeRemaining -= lastTickLength;
16040         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16041             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16042             if(suddenDeath) {
16043                 blackStartMove = forwardMostMove;
16044                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16045             }
16046         }
16047         DisplayBlackClock(blackTimeRemaining - fudge,
16048                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16049     }
16050     if (CheckFlags()) return;
16051
16052     tickStartTM = now;
16053     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16054     StartClockTimer(intendedTickLength);
16055
16056     /* if the time remaining has fallen below the alarm threshold, sound the
16057      * alarm. if the alarm has sounded and (due to a takeback or time control
16058      * with increment) the time remaining has increased to a level above the
16059      * threshold, reset the alarm so it can sound again.
16060      */
16061
16062     if (appData.icsActive && appData.icsAlarm) {
16063
16064         /* make sure we are dealing with the user's clock */
16065         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16066                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16067            )) return;
16068
16069         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16070             alarmSounded = FALSE;
16071         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16072             PlayAlarmSound();
16073             alarmSounded = TRUE;
16074         }
16075     }
16076 }
16077
16078
16079 /* A player has just moved, so stop the previously running
16080    clock and (if in clock mode) start the other one.
16081    We redisplay both clocks in case we're in ICS mode, because
16082    ICS gives us an update to both clocks after every move.
16083    Note that this routine is called *after* forwardMostMove
16084    is updated, so the last fractional tick must be subtracted
16085    from the color that is *not* on move now.
16086 */
16087 void
16088 SwitchClocks(int newMoveNr)
16089 {
16090     long lastTickLength;
16091     TimeMark now;
16092     int flagged = FALSE;
16093
16094     GetTimeMark(&now);
16095
16096     if (StopClockTimer() && appData.clockMode) {
16097         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16098         if (!WhiteOnMove(forwardMostMove)) {
16099             if(blackNPS >= 0) lastTickLength = 0;
16100             blackTimeRemaining -= lastTickLength;
16101            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16102 //         if(pvInfoList[forwardMostMove].time == -1)
16103                  pvInfoList[forwardMostMove].time =               // use GUI time
16104                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16105         } else {
16106            if(whiteNPS >= 0) lastTickLength = 0;
16107            whiteTimeRemaining -= lastTickLength;
16108            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16109 //         if(pvInfoList[forwardMostMove].time == -1)
16110                  pvInfoList[forwardMostMove].time =
16111                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16112         }
16113         flagged = CheckFlags();
16114     }
16115     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16116     CheckTimeControl();
16117
16118     if (flagged || !appData.clockMode) return;
16119
16120     switch (gameMode) {
16121       case MachinePlaysBlack:
16122       case MachinePlaysWhite:
16123       case BeginningOfGame:
16124         if (pausing) return;
16125         break;
16126
16127       case EditGame:
16128       case PlayFromGameFile:
16129       case IcsExamining:
16130         return;
16131
16132       default:
16133         break;
16134     }
16135
16136     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16137         if(WhiteOnMove(forwardMostMove))
16138              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16139         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16140     }
16141
16142     tickStartTM = now;
16143     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16144       whiteTimeRemaining : blackTimeRemaining);
16145     StartClockTimer(intendedTickLength);
16146 }
16147
16148
16149 /* Stop both clocks */
16150 void
16151 StopClocks()
16152 {
16153     long lastTickLength;
16154     TimeMark now;
16155
16156     if (!StopClockTimer()) return;
16157     if (!appData.clockMode) return;
16158
16159     GetTimeMark(&now);
16160
16161     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16162     if (WhiteOnMove(forwardMostMove)) {
16163         if(whiteNPS >= 0) lastTickLength = 0;
16164         whiteTimeRemaining -= lastTickLength;
16165         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16166     } else {
16167         if(blackNPS >= 0) lastTickLength = 0;
16168         blackTimeRemaining -= lastTickLength;
16169         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16170     }
16171     CheckFlags();
16172 }
16173
16174 /* Start clock of player on move.  Time may have been reset, so
16175    if clock is already running, stop and restart it. */
16176 void
16177 StartClocks()
16178 {
16179     (void) StopClockTimer(); /* in case it was running already */
16180     DisplayBothClocks();
16181     if (CheckFlags()) return;
16182
16183     if (!appData.clockMode) return;
16184     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16185
16186     GetTimeMark(&tickStartTM);
16187     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16188       whiteTimeRemaining : blackTimeRemaining);
16189
16190    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16191     whiteNPS = blackNPS = -1;
16192     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16193        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16194         whiteNPS = first.nps;
16195     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16196        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16197         blackNPS = first.nps;
16198     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16199         whiteNPS = second.nps;
16200     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16201         blackNPS = second.nps;
16202     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16203
16204     StartClockTimer(intendedTickLength);
16205 }
16206
16207 char *
16208 TimeString(ms)
16209      long ms;
16210 {
16211     long second, minute, hour, day;
16212     char *sign = "";
16213     static char buf[32];
16214
16215     if (ms > 0 && ms <= 9900) {
16216       /* convert milliseconds to tenths, rounding up */
16217       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16218
16219       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16220       return buf;
16221     }
16222
16223     /* convert milliseconds to seconds, rounding up */
16224     /* use floating point to avoid strangeness of integer division
16225        with negative dividends on many machines */
16226     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16227
16228     if (second < 0) {
16229         sign = "-";
16230         second = -second;
16231     }
16232
16233     day = second / (60 * 60 * 24);
16234     second = second % (60 * 60 * 24);
16235     hour = second / (60 * 60);
16236     second = second % (60 * 60);
16237     minute = second / 60;
16238     second = second % 60;
16239
16240     if (day > 0)
16241       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16242               sign, day, hour, minute, second);
16243     else if (hour > 0)
16244       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16245     else
16246       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16247
16248     return buf;
16249 }
16250
16251
16252 /*
16253  * This is necessary because some C libraries aren't ANSI C compliant yet.
16254  */
16255 char *
16256 StrStr(string, match)
16257      char *string, *match;
16258 {
16259     int i, length;
16260
16261     length = strlen(match);
16262
16263     for (i = strlen(string) - length; i >= 0; i--, string++)
16264       if (!strncmp(match, string, length))
16265         return string;
16266
16267     return NULL;
16268 }
16269
16270 char *
16271 StrCaseStr(string, match)
16272      char *string, *match;
16273 {
16274     int i, j, length;
16275
16276     length = strlen(match);
16277
16278     for (i = strlen(string) - length; i >= 0; i--, string++) {
16279         for (j = 0; j < length; j++) {
16280             if (ToLower(match[j]) != ToLower(string[j]))
16281               break;
16282         }
16283         if (j == length) return string;
16284     }
16285
16286     return NULL;
16287 }
16288
16289 #ifndef _amigados
16290 int
16291 StrCaseCmp(s1, s2)
16292      char *s1, *s2;
16293 {
16294     char c1, c2;
16295
16296     for (;;) {
16297         c1 = ToLower(*s1++);
16298         c2 = ToLower(*s2++);
16299         if (c1 > c2) return 1;
16300         if (c1 < c2) return -1;
16301         if (c1 == NULLCHAR) return 0;
16302     }
16303 }
16304
16305
16306 int
16307 ToLower(c)
16308      int c;
16309 {
16310     return isupper(c) ? tolower(c) : c;
16311 }
16312
16313
16314 int
16315 ToUpper(c)
16316      int c;
16317 {
16318     return islower(c) ? toupper(c) : c;
16319 }
16320 #endif /* !_amigados    */
16321
16322 char *
16323 StrSave(s)
16324      char *s;
16325 {
16326   char *ret;
16327
16328   if ((ret = (char *) malloc(strlen(s) + 1)))
16329     {
16330       safeStrCpy(ret, s, strlen(s)+1);
16331     }
16332   return ret;
16333 }
16334
16335 char *
16336 StrSavePtr(s, savePtr)
16337      char *s, **savePtr;
16338 {
16339     if (*savePtr) {
16340         free(*savePtr);
16341     }
16342     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16343       safeStrCpy(*savePtr, s, strlen(s)+1);
16344     }
16345     return(*savePtr);
16346 }
16347
16348 char *
16349 PGNDate()
16350 {
16351     time_t clock;
16352     struct tm *tm;
16353     char buf[MSG_SIZ];
16354
16355     clock = time((time_t *)NULL);
16356     tm = localtime(&clock);
16357     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16358             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16359     return StrSave(buf);
16360 }
16361
16362
16363 char *
16364 PositionToFEN(move, overrideCastling)
16365      int move;
16366      char *overrideCastling;
16367 {
16368     int i, j, fromX, fromY, toX, toY;
16369     int whiteToPlay;
16370     char buf[MSG_SIZ];
16371     char *p, *q;
16372     int emptycount;
16373     ChessSquare piece;
16374
16375     whiteToPlay = (gameMode == EditPosition) ?
16376       !blackPlaysFirst : (move % 2 == 0);
16377     p = buf;
16378
16379     /* Piece placement data */
16380     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16381         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16382         emptycount = 0;
16383         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16384             if (boards[move][i][j] == EmptySquare) {
16385                 emptycount++;
16386             } else { ChessSquare piece = boards[move][i][j];
16387                 if (emptycount > 0) {
16388                     if(emptycount<10) /* [HGM] can be >= 10 */
16389                         *p++ = '0' + emptycount;
16390                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16391                     emptycount = 0;
16392                 }
16393                 if(PieceToChar(piece) == '+') {
16394                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16395                     *p++ = '+';
16396                     piece = (ChessSquare)(DEMOTED piece);
16397                 }
16398                 *p++ = PieceToChar(piece);
16399                 if(p[-1] == '~') {
16400                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16401                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16402                     *p++ = '~';
16403                 }
16404             }
16405         }
16406         if (emptycount > 0) {
16407             if(emptycount<10) /* [HGM] can be >= 10 */
16408                 *p++ = '0' + emptycount;
16409             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16410             emptycount = 0;
16411         }
16412         *p++ = '/';
16413     }
16414     *(p - 1) = ' ';
16415
16416     /* [HGM] print Crazyhouse or Shogi holdings */
16417     if( gameInfo.holdingsWidth ) {
16418         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16419         q = p;
16420         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16421             piece = boards[move][i][BOARD_WIDTH-1];
16422             if( piece != EmptySquare )
16423               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16424                   *p++ = PieceToChar(piece);
16425         }
16426         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16427             piece = boards[move][BOARD_HEIGHT-i-1][0];
16428             if( piece != EmptySquare )
16429               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16430                   *p++ = PieceToChar(piece);
16431         }
16432
16433         if( q == p ) *p++ = '-';
16434         *p++ = ']';
16435         *p++ = ' ';
16436     }
16437
16438     /* Active color */
16439     *p++ = whiteToPlay ? 'w' : 'b';
16440     *p++ = ' ';
16441
16442   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16443     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16444   } else {
16445   if(nrCastlingRights) {
16446      q = p;
16447      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16448        /* [HGM] write directly from rights */
16449            if(boards[move][CASTLING][2] != NoRights &&
16450               boards[move][CASTLING][0] != NoRights   )
16451                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16452            if(boards[move][CASTLING][2] != NoRights &&
16453               boards[move][CASTLING][1] != NoRights   )
16454                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16455            if(boards[move][CASTLING][5] != NoRights &&
16456               boards[move][CASTLING][3] != NoRights   )
16457                 *p++ = boards[move][CASTLING][3] + AAA;
16458            if(boards[move][CASTLING][5] != NoRights &&
16459               boards[move][CASTLING][4] != NoRights   )
16460                 *p++ = boards[move][CASTLING][4] + AAA;
16461      } else {
16462
16463         /* [HGM] write true castling rights */
16464         if( nrCastlingRights == 6 ) {
16465             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16466                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16467             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16468                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16469             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16470                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16471             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16472                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16473         }
16474      }
16475      if (q == p) *p++ = '-'; /* No castling rights */
16476      *p++ = ' ';
16477   }
16478
16479   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16480      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16481     /* En passant target square */
16482     if (move > backwardMostMove) {
16483         fromX = moveList[move - 1][0] - AAA;
16484         fromY = moveList[move - 1][1] - ONE;
16485         toX = moveList[move - 1][2] - AAA;
16486         toY = moveList[move - 1][3] - ONE;
16487         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16488             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16489             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16490             fromX == toX) {
16491             /* 2-square pawn move just happened */
16492             *p++ = toX + AAA;
16493             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16494         } else {
16495             *p++ = '-';
16496         }
16497     } else if(move == backwardMostMove) {
16498         // [HGM] perhaps we should always do it like this, and forget the above?
16499         if((signed char)boards[move][EP_STATUS] >= 0) {
16500             *p++ = boards[move][EP_STATUS] + AAA;
16501             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16502         } else {
16503             *p++ = '-';
16504         }
16505     } else {
16506         *p++ = '-';
16507     }
16508     *p++ = ' ';
16509   }
16510   }
16511
16512     /* [HGM] find reversible plies */
16513     {   int i = 0, j=move;
16514
16515         if (appData.debugMode) { int k;
16516             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16517             for(k=backwardMostMove; k<=forwardMostMove; k++)
16518                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16519
16520         }
16521
16522         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16523         if( j == backwardMostMove ) i += initialRulePlies;
16524         sprintf(p, "%d ", i);
16525         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16526     }
16527     /* Fullmove number */
16528     sprintf(p, "%d", (move / 2) + 1);
16529
16530     return StrSave(buf);
16531 }
16532
16533 Boolean
16534 ParseFEN(board, blackPlaysFirst, fen)
16535     Board board;
16536      int *blackPlaysFirst;
16537      char *fen;
16538 {
16539     int i, j;
16540     char *p, c;
16541     int emptycount;
16542     ChessSquare piece;
16543
16544     p = fen;
16545
16546     /* [HGM] by default clear Crazyhouse holdings, if present */
16547     if(gameInfo.holdingsWidth) {
16548        for(i=0; i<BOARD_HEIGHT; i++) {
16549            board[i][0]             = EmptySquare; /* black holdings */
16550            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16551            board[i][1]             = (ChessSquare) 0; /* black counts */
16552            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16553        }
16554     }
16555
16556     /* Piece placement data */
16557     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16558         j = 0;
16559         for (;;) {
16560             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16561                 if (*p == '/') p++;
16562                 emptycount = gameInfo.boardWidth - j;
16563                 while (emptycount--)
16564                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16565                 break;
16566 #if(BOARD_FILES >= 10)
16567             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16568                 p++; emptycount=10;
16569                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16570                 while (emptycount--)
16571                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16572 #endif
16573             } else if (isdigit(*p)) {
16574                 emptycount = *p++ - '0';
16575                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16576                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16577                 while (emptycount--)
16578                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16579             } else if (*p == '+' || isalpha(*p)) {
16580                 if (j >= gameInfo.boardWidth) return FALSE;
16581                 if(*p=='+') {
16582                     piece = CharToPiece(*++p);
16583                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16584                     piece = (ChessSquare) (PROMOTED piece ); p++;
16585                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16586                 } else piece = CharToPiece(*p++);
16587
16588                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16589                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16590                     piece = (ChessSquare) (PROMOTED piece);
16591                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16592                     p++;
16593                 }
16594                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16595             } else {
16596                 return FALSE;
16597             }
16598         }
16599     }
16600     while (*p == '/' || *p == ' ') p++;
16601
16602     /* [HGM] look for Crazyhouse holdings here */
16603     while(*p==' ') p++;
16604     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16605         if(*p == '[') p++;
16606         if(*p == '-' ) p++; /* empty holdings */ else {
16607             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16608             /* if we would allow FEN reading to set board size, we would   */
16609             /* have to add holdings and shift the board read so far here   */
16610             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16611                 p++;
16612                 if((int) piece >= (int) BlackPawn ) {
16613                     i = (int)piece - (int)BlackPawn;
16614                     i = PieceToNumber((ChessSquare)i);
16615                     if( i >= gameInfo.holdingsSize ) return FALSE;
16616                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16617                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16618                 } else {
16619                     i = (int)piece - (int)WhitePawn;
16620                     i = PieceToNumber((ChessSquare)i);
16621                     if( i >= gameInfo.holdingsSize ) return FALSE;
16622                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16623                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16624                 }
16625             }
16626         }
16627         if(*p == ']') p++;
16628     }
16629
16630     while(*p == ' ') p++;
16631
16632     /* Active color */
16633     c = *p++;
16634     if(appData.colorNickNames) {
16635       if( c == appData.colorNickNames[0] ) c = 'w'; else
16636       if( c == appData.colorNickNames[1] ) c = 'b';
16637     }
16638     switch (c) {
16639       case 'w':
16640         *blackPlaysFirst = FALSE;
16641         break;
16642       case 'b':
16643         *blackPlaysFirst = TRUE;
16644         break;
16645       default:
16646         return FALSE;
16647     }
16648
16649     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16650     /* return the extra info in global variiables             */
16651
16652     /* set defaults in case FEN is incomplete */
16653     board[EP_STATUS] = EP_UNKNOWN;
16654     for(i=0; i<nrCastlingRights; i++ ) {
16655         board[CASTLING][i] =
16656             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16657     }   /* assume possible unless obviously impossible */
16658     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16659     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16660     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16661                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16662     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16663     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16664     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16665                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16666     FENrulePlies = 0;
16667
16668     while(*p==' ') p++;
16669     if(nrCastlingRights) {
16670       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16671           /* castling indicator present, so default becomes no castlings */
16672           for(i=0; i<nrCastlingRights; i++ ) {
16673                  board[CASTLING][i] = NoRights;
16674           }
16675       }
16676       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16677              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16678              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16679              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16680         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16681
16682         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16683             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16684             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16685         }
16686         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16687             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16688         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16689                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16690         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16691                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16692         switch(c) {
16693           case'K':
16694               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16695               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16696               board[CASTLING][2] = whiteKingFile;
16697               break;
16698           case'Q':
16699               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16700               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16701               board[CASTLING][2] = whiteKingFile;
16702               break;
16703           case'k':
16704               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16705               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16706               board[CASTLING][5] = blackKingFile;
16707               break;
16708           case'q':
16709               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16710               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16711               board[CASTLING][5] = blackKingFile;
16712           case '-':
16713               break;
16714           default: /* FRC castlings */
16715               if(c >= 'a') { /* black rights */
16716                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16717                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16718                   if(i == BOARD_RGHT) break;
16719                   board[CASTLING][5] = i;
16720                   c -= AAA;
16721                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16722                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16723                   if(c > i)
16724                       board[CASTLING][3] = c;
16725                   else
16726                       board[CASTLING][4] = c;
16727               } else { /* white rights */
16728                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16729                     if(board[0][i] == WhiteKing) break;
16730                   if(i == BOARD_RGHT) break;
16731                   board[CASTLING][2] = i;
16732                   c -= AAA - 'a' + 'A';
16733                   if(board[0][c] >= WhiteKing) break;
16734                   if(c > i)
16735                       board[CASTLING][0] = c;
16736                   else
16737                       board[CASTLING][1] = c;
16738               }
16739         }
16740       }
16741       for(i=0; i<nrCastlingRights; i++)
16742         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16743     if (appData.debugMode) {
16744         fprintf(debugFP, "FEN castling rights:");
16745         for(i=0; i<nrCastlingRights; i++)
16746         fprintf(debugFP, " %d", board[CASTLING][i]);
16747         fprintf(debugFP, "\n");
16748     }
16749
16750       while(*p==' ') p++;
16751     }
16752
16753     /* read e.p. field in games that know e.p. capture */
16754     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16755        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16756       if(*p=='-') {
16757         p++; board[EP_STATUS] = EP_NONE;
16758       } else {
16759          char c = *p++ - AAA;
16760
16761          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16762          if(*p >= '0' && *p <='9') p++;
16763          board[EP_STATUS] = c;
16764       }
16765     }
16766
16767
16768     if(sscanf(p, "%d", &i) == 1) {
16769         FENrulePlies = i; /* 50-move ply counter */
16770         /* (The move number is still ignored)    */
16771     }
16772
16773     return TRUE;
16774 }
16775
16776 void
16777 EditPositionPasteFEN(char *fen)
16778 {
16779   if (fen != NULL) {
16780     Board initial_position;
16781
16782     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16783       DisplayError(_("Bad FEN position in clipboard"), 0);
16784       return ;
16785     } else {
16786       int savedBlackPlaysFirst = blackPlaysFirst;
16787       EditPositionEvent();
16788       blackPlaysFirst = savedBlackPlaysFirst;
16789       CopyBoard(boards[0], initial_position);
16790       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16791       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16792       DisplayBothClocks();
16793       DrawPosition(FALSE, boards[currentMove]);
16794     }
16795   }
16796 }
16797
16798 static char cseq[12] = "\\   ";
16799
16800 Boolean set_cont_sequence(char *new_seq)
16801 {
16802     int len;
16803     Boolean ret;
16804
16805     // handle bad attempts to set the sequence
16806         if (!new_seq)
16807                 return 0; // acceptable error - no debug
16808
16809     len = strlen(new_seq);
16810     ret = (len > 0) && (len < sizeof(cseq));
16811     if (ret)
16812       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16813     else if (appData.debugMode)
16814       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16815     return ret;
16816 }
16817
16818 /*
16819     reformat a source message so words don't cross the width boundary.  internal
16820     newlines are not removed.  returns the wrapped size (no null character unless
16821     included in source message).  If dest is NULL, only calculate the size required
16822     for the dest buffer.  lp argument indicats line position upon entry, and it's
16823     passed back upon exit.
16824 */
16825 int wrap(char *dest, char *src, int count, int width, int *lp)
16826 {
16827     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16828
16829     cseq_len = strlen(cseq);
16830     old_line = line = *lp;
16831     ansi = len = clen = 0;
16832
16833     for (i=0; i < count; i++)
16834     {
16835         if (src[i] == '\033')
16836             ansi = 1;
16837
16838         // if we hit the width, back up
16839         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16840         {
16841             // store i & len in case the word is too long
16842             old_i = i, old_len = len;
16843
16844             // find the end of the last word
16845             while (i && src[i] != ' ' && src[i] != '\n')
16846             {
16847                 i--;
16848                 len--;
16849             }
16850
16851             // word too long?  restore i & len before splitting it
16852             if ((old_i-i+clen) >= width)
16853             {
16854                 i = old_i;
16855                 len = old_len;
16856             }
16857
16858             // extra space?
16859             if (i && src[i-1] == ' ')
16860                 len--;
16861
16862             if (src[i] != ' ' && src[i] != '\n')
16863             {
16864                 i--;
16865                 if (len)
16866                     len--;
16867             }
16868
16869             // now append the newline and continuation sequence
16870             if (dest)
16871                 dest[len] = '\n';
16872             len++;
16873             if (dest)
16874                 strncpy(dest+len, cseq, cseq_len);
16875             len += cseq_len;
16876             line = cseq_len;
16877             clen = cseq_len;
16878             continue;
16879         }
16880
16881         if (dest)
16882             dest[len] = src[i];
16883         len++;
16884         if (!ansi)
16885             line++;
16886         if (src[i] == '\n')
16887             line = 0;
16888         if (src[i] == 'm')
16889             ansi = 0;
16890     }
16891     if (dest && appData.debugMode)
16892     {
16893         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16894             count, width, line, len, *lp);
16895         show_bytes(debugFP, src, count);
16896         fprintf(debugFP, "\ndest: ");
16897         show_bytes(debugFP, dest, len);
16898         fprintf(debugFP, "\n");
16899     }
16900     *lp = dest ? line : old_line;
16901
16902     return len;
16903 }
16904
16905 // [HGM] vari: routines for shelving variations
16906 Boolean modeRestore = FALSE;
16907
16908 void
16909 PushInner(int firstMove, int lastMove)
16910 {
16911         int i, j, nrMoves = lastMove - firstMove;
16912
16913         // push current tail of game on stack
16914         savedResult[storedGames] = gameInfo.result;
16915         savedDetails[storedGames] = gameInfo.resultDetails;
16916         gameInfo.resultDetails = NULL;
16917         savedFirst[storedGames] = firstMove;
16918         savedLast [storedGames] = lastMove;
16919         savedFramePtr[storedGames] = framePtr;
16920         framePtr -= nrMoves; // reserve space for the boards
16921         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16922             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16923             for(j=0; j<MOVE_LEN; j++)
16924                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16925             for(j=0; j<2*MOVE_LEN; j++)
16926                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16927             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16928             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16929             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16930             pvInfoList[firstMove+i-1].depth = 0;
16931             commentList[framePtr+i] = commentList[firstMove+i];
16932             commentList[firstMove+i] = NULL;
16933         }
16934
16935         storedGames++;
16936         forwardMostMove = firstMove; // truncate game so we can start variation
16937 }
16938
16939 void
16940 PushTail(int firstMove, int lastMove)
16941 {
16942         if(appData.icsActive) { // only in local mode
16943                 forwardMostMove = currentMove; // mimic old ICS behavior
16944                 return;
16945         }
16946         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16947
16948         PushInner(firstMove, lastMove);
16949         if(storedGames == 1) GreyRevert(FALSE);
16950         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16951 }
16952
16953 void
16954 PopInner(Boolean annotate)
16955 {
16956         int i, j, nrMoves;
16957         char buf[8000], moveBuf[20];
16958
16959         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16960         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16961         nrMoves = savedLast[storedGames] - currentMove;
16962         if(annotate) {
16963                 int cnt = 10;
16964                 if(!WhiteOnMove(currentMove))
16965                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16966                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16967                 for(i=currentMove; i<forwardMostMove; i++) {
16968                         if(WhiteOnMove(i))
16969                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16970                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16971                         strcat(buf, moveBuf);
16972                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16973                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16974                 }
16975                 strcat(buf, ")");
16976         }
16977         for(i=1; i<=nrMoves; i++) { // copy last variation back
16978             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16979             for(j=0; j<MOVE_LEN; j++)
16980                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16981             for(j=0; j<2*MOVE_LEN; j++)
16982                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16983             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16984             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16985             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16986             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16987             commentList[currentMove+i] = commentList[framePtr+i];
16988             commentList[framePtr+i] = NULL;
16989         }
16990         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16991         framePtr = savedFramePtr[storedGames];
16992         gameInfo.result = savedResult[storedGames];
16993         if(gameInfo.resultDetails != NULL) {
16994             free(gameInfo.resultDetails);
16995       }
16996         gameInfo.resultDetails = savedDetails[storedGames];
16997         forwardMostMove = currentMove + nrMoves;
16998 }
16999
17000 Boolean
17001 PopTail(Boolean annotate)
17002 {
17003         if(appData.icsActive) return FALSE; // only in local mode
17004         if(!storedGames) return FALSE; // sanity
17005         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17006
17007         PopInner(annotate);
17008         if(currentMove < forwardMostMove) ForwardEvent(); else
17009         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17010
17011         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17012         return TRUE;
17013 }
17014
17015 void
17016 CleanupTail()
17017 {       // remove all shelved variations
17018         int i;
17019         for(i=0; i<storedGames; i++) {
17020             if(savedDetails[i])
17021                 free(savedDetails[i]);
17022             savedDetails[i] = NULL;
17023         }
17024         for(i=framePtr; i<MAX_MOVES; i++) {
17025                 if(commentList[i]) free(commentList[i]);
17026                 commentList[i] = NULL;
17027         }
17028         framePtr = MAX_MOVES-1;
17029         storedGames = 0;
17030 }
17031
17032 void
17033 LoadVariation(int index, char *text)
17034 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17035         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17036         int level = 0, move;
17037
17038         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17039         // first find outermost bracketing variation
17040         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17041             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17042                 if(*p == '{') wait = '}'; else
17043                 if(*p == '[') wait = ']'; else
17044                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17045                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17046             }
17047             if(*p == wait) wait = NULLCHAR; // closing ]} found
17048             p++;
17049         }
17050         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17051         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17052         end[1] = NULLCHAR; // clip off comment beyond variation
17053         ToNrEvent(currentMove-1);
17054         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17055         // kludge: use ParsePV() to append variation to game
17056         move = currentMove;
17057         ParsePV(start, TRUE, TRUE);
17058         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17059         ClearPremoveHighlights();
17060         CommentPopDown();
17061         ToNrEvent(currentMove+1);
17062 }
17063