Reset protocol version before loading new engine
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "gettext.h"
133
134 #ifdef ENABLE_NLS
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
138 #else
139 # ifdef WIN32
140 #   define _(s) T_(s)
141 #   define N_(s) s
142 # else
143 #   define _(s) (s)
144 #   define N_(s) s
145 #   define T_(s) s
146 # endif
147 #endif
148
149
150 int establish P((void));
151 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
152                          char *buf, int count, int error));
153 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
154                       char *buf, int count, int error));
155 void ics_printf P((char *format, ...));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225
226 #ifdef WIN32
227        extern void ConsoleCreate();
228 #endif
229
230 ChessProgramState *WhitePlayer();
231 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
232 int VerifyDisplayMode P(());
233
234 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
235 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
236 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
237 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
238 void ics_update_width P((int new_width));
239 extern char installDir[MSG_SIZ];
240 VariantClass startVariant; /* [HGM] nicks: initial variant */
241 Boolean abortMatch;
242
243 extern int tinyLayout, smallLayout;
244 ChessProgramStats programStats;
245 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
246 int endPV = -1;
247 static int exiting = 0; /* [HGM] moved to top */
248 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
249 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
250 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
251 int partnerHighlight[2];
252 Boolean partnerBoardValid = 0;
253 char partnerStatus[MSG_SIZ];
254 Boolean partnerUp;
255 Boolean originalFlip;
256 Boolean twoBoards = 0;
257 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
258 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
259 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
260 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
261 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
262 int opponentKibitzes;
263 int lastSavedGame; /* [HGM] save: ID of game */
264 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
265 extern int chatCount;
266 int chattingPartner;
267 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
268 char lastMsg[MSG_SIZ];
269 ChessSquare pieceSweep = EmptySquare;
270 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
271 int promoDefaultAltered;
272
273 /* States for ics_getting_history */
274 #define H_FALSE 0
275 #define H_REQUESTED 1
276 #define H_GOT_REQ_HEADER 2
277 #define H_GOT_UNREQ_HEADER 3
278 #define H_GETTING_MOVES 4
279 #define H_GOT_UNWANTED_HEADER 5
280
281 /* whosays values for GameEnds */
282 #define GE_ICS 0
283 #define GE_ENGINE 1
284 #define GE_PLAYER 2
285 #define GE_FILE 3
286 #define GE_XBOARD 4
287 #define GE_ENGINE1 5
288 #define GE_ENGINE2 6
289
290 /* Maximum number of games in a cmail message */
291 #define CMAIL_MAX_GAMES 20
292
293 /* Different types of move when calling RegisterMove */
294 #define CMAIL_MOVE   0
295 #define CMAIL_RESIGN 1
296 #define CMAIL_DRAW   2
297 #define CMAIL_ACCEPT 3
298
299 /* Different types of result to remember for each game */
300 #define CMAIL_NOT_RESULT 0
301 #define CMAIL_OLD_RESULT 1
302 #define CMAIL_NEW_RESULT 2
303
304 /* Telnet protocol constants */
305 #define TN_WILL 0373
306 #define TN_WONT 0374
307 #define TN_DO   0375
308 #define TN_DONT 0376
309 #define TN_IAC  0377
310 #define TN_ECHO 0001
311 #define TN_SGA  0003
312 #define TN_PORT 23
313
314 char*
315 safeStrCpy (char *dst, const char *src, size_t count)
316 { // [HGM] made safe
317   int i;
318   assert( dst != NULL );
319   assert( src != NULL );
320   assert( count > 0 );
321
322   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
323   if(  i == count && dst[count-1] != NULLCHAR)
324     {
325       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
326       if(appData.debugMode)
327       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
328     }
329
330   return dst;
331 }
332
333 /* Some compiler can't cast u64 to double
334  * This function do the job for us:
335
336  * We use the highest bit for cast, this only
337  * works if the highest bit is not
338  * in use (This should not happen)
339  *
340  * We used this for all compiler
341  */
342 double
343 u64ToDouble (u64 value)
344 {
345   double r;
346   u64 tmp = value & u64Const(0x7fffffffffffffff);
347   r = (double)(s64)tmp;
348   if (value & u64Const(0x8000000000000000))
349        r +=  9.2233720368547758080e18; /* 2^63 */
350  return r;
351 }
352
353 /* Fake up flags for now, as we aren't keeping track of castling
354    availability yet. [HGM] Change of logic: the flag now only
355    indicates the type of castlings allowed by the rule of the game.
356    The actual rights themselves are maintained in the array
357    castlingRights, as part of the game history, and are not probed
358    by this function.
359  */
360 int
361 PosFlags (index)
362 {
363   int flags = F_ALL_CASTLE_OK;
364   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
365   switch (gameInfo.variant) {
366   case VariantSuicide:
367     flags &= ~F_ALL_CASTLE_OK;
368   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
369     flags |= F_IGNORE_CHECK;
370   case VariantLosers:
371     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
372     break;
373   case VariantAtomic:
374     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
375     break;
376   case VariantKriegspiel:
377     flags |= F_KRIEGSPIEL_CAPTURE;
378     break;
379   case VariantCapaRandom:
380   case VariantFischeRandom:
381     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
382   case VariantNoCastle:
383   case VariantShatranj:
384   case VariantCourier:
385   case VariantMakruk:
386   case VariantGrand:
387     flags &= ~F_ALL_CASTLE_OK;
388     break;
389   default:
390     break;
391   }
392   return flags;
393 }
394
395 FILE *gameFileFP, *debugFP;
396
397 /*
398     [AS] Note: sometimes, the sscanf() function is used to parse the input
399     into a fixed-size buffer. Because of this, we must be prepared to
400     receive strings as long as the size of the input buffer, which is currently
401     set to 4K for Windows and 8K for the rest.
402     So, we must either allocate sufficiently large buffers here, or
403     reduce the size of the input buffer in the input reading part.
404 */
405
406 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
407 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
408 char thinkOutput1[MSG_SIZ*10];
409
410 ChessProgramState first, second, pairing;
411
412 /* premove variables */
413 int premoveToX = 0;
414 int premoveToY = 0;
415 int premoveFromX = 0;
416 int premoveFromY = 0;
417 int premovePromoChar = 0;
418 int gotPremove = 0;
419 Boolean alarmSounded;
420 /* end premove variables */
421
422 char *ics_prefix = "$";
423 int ics_type = ICS_GENERIC;
424
425 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
426 int pauseExamForwardMostMove = 0;
427 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
428 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
429 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
430 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
431 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
432 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
433 int whiteFlag = FALSE, blackFlag = FALSE;
434 int userOfferedDraw = FALSE;
435 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
436 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
437 int cmailMoveType[CMAIL_MAX_GAMES];
438 long ics_clock_paused = 0;
439 ProcRef icsPR = NoProc, cmailPR = NoProc;
440 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
441 GameMode gameMode = BeginningOfGame;
442 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
443 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
444 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
445 int hiddenThinkOutputState = 0; /* [AS] */
446 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
447 int adjudicateLossPlies = 6;
448 char white_holding[64], black_holding[64];
449 TimeMark lastNodeCountTime;
450 long lastNodeCount=0;
451 int shiftKey; // [HGM] set by mouse handler
452
453 int have_sent_ICS_logon = 0;
454 int movesPerSession;
455 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
456 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
457 Boolean adjustedClock;
458 long timeControl_2; /* [AS] Allow separate time controls */
459 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
460 long timeRemaining[2][MAX_MOVES];
461 int matchGame = 0, nextGame = 0, roundNr = 0;
462 Boolean waitingForGame = FALSE;
463 TimeMark programStartTime, pauseStart;
464 char ics_handle[MSG_SIZ];
465 int have_set_title = 0;
466
467 /* animateTraining preserves the state of appData.animate
468  * when Training mode is activated. This allows the
469  * response to be animated when appData.animate == TRUE and
470  * appData.animateDragging == TRUE.
471  */
472 Boolean animateTraining;
473
474 GameInfo gameInfo;
475
476 AppData appData;
477
478 Board boards[MAX_MOVES];
479 /* [HGM] Following 7 needed for accurate legality tests: */
480 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
481 signed char  initialRights[BOARD_FILES];
482 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
483 int   initialRulePlies, FENrulePlies;
484 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
485 int loadFlag = 0;
486 Boolean shuffleOpenings;
487 int mute; // mute all sounds
488
489 // [HGM] vari: next 12 to save and restore variations
490 #define MAX_VARIATIONS 10
491 int framePtr = MAX_MOVES-1; // points to free stack entry
492 int storedGames = 0;
493 int savedFirst[MAX_VARIATIONS];
494 int savedLast[MAX_VARIATIONS];
495 int savedFramePtr[MAX_VARIATIONS];
496 char *savedDetails[MAX_VARIATIONS];
497 ChessMove savedResult[MAX_VARIATIONS];
498
499 void PushTail P((int firstMove, int lastMove));
500 Boolean PopTail P((Boolean annotate));
501 void PushInner P((int firstMove, int lastMove));
502 void PopInner P((Boolean annotate));
503 void CleanupTail P((void));
504
505 ChessSquare  FIDEArray[2][BOARD_FILES] = {
506     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
507         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
508     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
509         BlackKing, BlackBishop, BlackKnight, BlackRook }
510 };
511
512 ChessSquare twoKingsArray[2][BOARD_FILES] = {
513     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
514         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
515     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
516         BlackKing, BlackKing, BlackKnight, BlackRook }
517 };
518
519 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
520     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
521         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
522     { BlackRook, BlackMan, BlackBishop, BlackQueen,
523         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
524 };
525
526 ChessSquare SpartanArray[2][BOARD_FILES] = {
527     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
528         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
529     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
530         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
531 };
532
533 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
534     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
535         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
536     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
537         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
538 };
539
540 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
541     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
542         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
544         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
545 };
546
547 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
548     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
549         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
550     { BlackRook, BlackKnight, BlackMan, BlackFerz,
551         BlackKing, BlackMan, BlackKnight, BlackRook }
552 };
553
554
555 #if (BOARD_FILES>=10)
556 ChessSquare ShogiArray[2][BOARD_FILES] = {
557     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
558         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
559     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
560         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
561 };
562
563 ChessSquare XiangqiArray[2][BOARD_FILES] = {
564     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
565         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
566     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
567         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
568 };
569
570 ChessSquare CapablancaArray[2][BOARD_FILES] = {
571     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
572         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
573     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
574         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
575 };
576
577 ChessSquare GreatArray[2][BOARD_FILES] = {
578     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
579         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
580     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
581         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
582 };
583
584 ChessSquare JanusArray[2][BOARD_FILES] = {
585     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
586         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
587     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
588         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
589 };
590
591 ChessSquare GrandArray[2][BOARD_FILES] = {
592     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
593         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
594     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
595         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
596 };
597
598 #ifdef GOTHIC
599 ChessSquare GothicArray[2][BOARD_FILES] = {
600     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
601         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
602     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
603         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
604 };
605 #else // !GOTHIC
606 #define GothicArray CapablancaArray
607 #endif // !GOTHIC
608
609 #ifdef FALCON
610 ChessSquare FalconArray[2][BOARD_FILES] = {
611     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
612         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
613     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
614         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
615 };
616 #else // !FALCON
617 #define FalconArray CapablancaArray
618 #endif // !FALCON
619
620 #else // !(BOARD_FILES>=10)
621 #define XiangqiPosition FIDEArray
622 #define CapablancaArray FIDEArray
623 #define GothicArray FIDEArray
624 #define GreatArray FIDEArray
625 #endif // !(BOARD_FILES>=10)
626
627 #if (BOARD_FILES>=12)
628 ChessSquare CourierArray[2][BOARD_FILES] = {
629     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
630         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
631     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
632         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
633 };
634 #else // !(BOARD_FILES>=12)
635 #define CourierArray CapablancaArray
636 #endif // !(BOARD_FILES>=12)
637
638
639 Board initialPosition;
640
641
642 /* Convert str to a rating. Checks for special cases of "----",
643
644    "++++", etc. Also strips ()'s */
645 int
646 string_to_rating (char *str)
647 {
648   while(*str && !isdigit(*str)) ++str;
649   if (!*str)
650     return 0;   /* One of the special "no rating" cases */
651   else
652     return atoi(str);
653 }
654
655 void
656 ClearProgramStats ()
657 {
658     /* Init programStats */
659     programStats.movelist[0] = 0;
660     programStats.depth = 0;
661     programStats.nr_moves = 0;
662     programStats.moves_left = 0;
663     programStats.nodes = 0;
664     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
665     programStats.score = 0;
666     programStats.got_only_move = 0;
667     programStats.got_fail = 0;
668     programStats.line_is_book = 0;
669 }
670
671 void
672 CommonEngineInit ()
673 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
674     if (appData.firstPlaysBlack) {
675         first.twoMachinesColor = "black\n";
676         second.twoMachinesColor = "white\n";
677     } else {
678         first.twoMachinesColor = "white\n";
679         second.twoMachinesColor = "black\n";
680     }
681
682     first.other = &second;
683     second.other = &first;
684
685     { float norm = 1;
686         if(appData.timeOddsMode) {
687             norm = appData.timeOdds[0];
688             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
689         }
690         first.timeOdds  = appData.timeOdds[0]/norm;
691         second.timeOdds = appData.timeOdds[1]/norm;
692     }
693
694     if(programVersion) free(programVersion);
695     if (appData.noChessProgram) {
696         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
697         sprintf(programVersion, "%s", PACKAGE_STRING);
698     } else {
699       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
700       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
701       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
702     }
703 }
704
705 void
706 UnloadEngine (ChessProgramState *cps)
707 {
708         /* Kill off first chess program */
709         if (cps->isr != NULL)
710           RemoveInputSource(cps->isr);
711         cps->isr = NULL;
712
713         if (cps->pr != NoProc) {
714             ExitAnalyzeMode();
715             DoSleep( appData.delayBeforeQuit );
716             SendToProgram("quit\n", cps);
717             DoSleep( appData.delayAfterQuit );
718             DestroyChildProcess(cps->pr, cps->useSigterm);
719         }
720         cps->pr = NoProc;
721         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
722 }
723
724 void
725 ClearOptions (ChessProgramState *cps)
726 {
727     int i;
728     cps->nrOptions = cps->comboCnt = 0;
729     for(i=0; i<MAX_OPTIONS; i++) {
730         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
731         cps->option[i].textValue = 0;
732     }
733 }
734
735 char *engineNames[] = {
736 "first",
737 "second"
738 };
739
740 void
741 InitEngine (ChessProgramState *cps, int n)
742 {   // [HGM] all engine initialiation put in a function that does one engine
743
744     ClearOptions(cps);
745
746     cps->which = engineNames[n];
747     cps->maybeThinking = FALSE;
748     cps->pr = NoProc;
749     cps->isr = NULL;
750     cps->sendTime = 2;
751     cps->sendDrawOffers = 1;
752
753     cps->program = appData.chessProgram[n];
754     cps->host = appData.host[n];
755     cps->dir = appData.directory[n];
756     cps->initString = appData.engInitString[n];
757     cps->computerString = appData.computerString[n];
758     cps->useSigint  = TRUE;
759     cps->useSigterm = TRUE;
760     cps->reuse = appData.reuse[n];
761     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
762     cps->useSetboard = FALSE;
763     cps->useSAN = FALSE;
764     cps->usePing = FALSE;
765     cps->lastPing = 0;
766     cps->lastPong = 0;
767     cps->usePlayother = FALSE;
768     cps->useColors = TRUE;
769     cps->useUsermove = FALSE;
770     cps->sendICS = FALSE;
771     cps->sendName = appData.icsActive;
772     cps->sdKludge = FALSE;
773     cps->stKludge = FALSE;
774     TidyProgramName(cps->program, cps->host, cps->tidy);
775     cps->matchWins = 0;
776     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
777     cps->analysisSupport = 2; /* detect */
778     cps->analyzing = FALSE;
779     cps->initDone = FALSE;
780
781     /* New features added by Tord: */
782     cps->useFEN960 = FALSE;
783     cps->useOOCastle = TRUE;
784     /* End of new features added by Tord. */
785     cps->fenOverride  = appData.fenOverride[n];
786
787     /* [HGM] time odds: set factor for each machine */
788     cps->timeOdds  = appData.timeOdds[n];
789
790     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
791     cps->accumulateTC = appData.accumulateTC[n];
792     cps->maxNrOfSessions = 1;
793
794     /* [HGM] debug */
795     cps->debug = FALSE;
796
797     cps->supportsNPS = UNKNOWN;
798     cps->memSize = FALSE;
799     cps->maxCores = FALSE;
800     cps->egtFormats[0] = NULLCHAR;
801
802     /* [HGM] options */
803     cps->optionSettings  = appData.engOptions[n];
804
805     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
806     cps->isUCI = appData.isUCI[n]; /* [AS] */
807     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
808
809     if (appData.protocolVersion[n] > PROTOVER
810         || appData.protocolVersion[n] < 1)
811       {
812         char buf[MSG_SIZ];
813         int len;
814
815         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
816                        appData.protocolVersion[n]);
817         if( (len >= MSG_SIZ) && appData.debugMode )
818           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
819
820         DisplayFatalError(buf, 0, 2);
821       }
822     else
823       {
824         cps->protocolVersion = appData.protocolVersion[n];
825       }
826
827     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
828     ParseFeatures(appData.featureDefaults, cps);
829 }
830
831 ChessProgramState *savCps;
832
833 void
834 LoadEngine ()
835 {
836     int i;
837     if(WaitForEngine(savCps, LoadEngine)) return;
838     CommonEngineInit(); // recalculate time odds
839     if(gameInfo.variant != StringToVariant(appData.variant)) {
840         // we changed variant when loading the engine; this forces us to reset
841         Reset(TRUE, savCps != &first);
842         EditGameEvent(); // for consistency with other path, as Reset changes mode
843     }
844     InitChessProgram(savCps, FALSE);
845     SendToProgram("force\n", savCps);
846     DisplayMessage("", "");
847     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
848     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
849     ThawUI();
850     SetGNUMode();
851 }
852
853 void
854 ReplaceEngine (ChessProgramState *cps, int n)
855 {
856     EditGameEvent();
857     UnloadEngine(cps);
858     appData.noChessProgram = FALSE;
859     appData.clockMode = TRUE;
860     InitEngine(cps, n);
861     UpdateLogos(TRUE);
862     if(n) return; // only startup first engine immediately; second can wait
863     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
864     LoadEngine();
865 }
866
867 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
868 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
869
870 static char resetOptions[] = 
871         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
872         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
873         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
874
875 void
876 Load (ChessProgramState *cps, int i)
877 {
878     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
879     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
880         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
881         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
882         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
883         appData.firstProtocolVersion = PROTOVER;
884         ParseArgsFromString(buf);
885         SwapEngines(i);
886         ReplaceEngine(cps, i);
887         return;
888     }
889     p = engineName;
890     while(q = strchr(p, SLASH)) p = q+1;
891     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
892     if(engineDir[0] != NULLCHAR)
893         appData.directory[i] = engineDir;
894     else if(p != engineName) { // derive directory from engine path, when not given
895         p[-1] = 0;
896         appData.directory[i] = strdup(engineName);
897         p[-1] = SLASH;
898     } else appData.directory[i] = ".";
899     if(params[0]) {
900         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
901         snprintf(command, MSG_SIZ, "%s %s", p, params);
902         p = command;
903     }
904     appData.chessProgram[i] = strdup(p);
905     appData.isUCI[i] = isUCI;
906     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
907     appData.hasOwnBookUCI[i] = hasBook;
908     if(!nickName[0]) useNick = FALSE;
909     if(useNick) ASSIGN(appData.pgnName[i], nickName);
910     if(addToList) {
911         int len;
912         char quote;
913         q = firstChessProgramNames;
914         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
915         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
916         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
917                         quote, p, quote, appData.directory[i], 
918                         useNick ? " -fn \"" : "",
919                         useNick ? nickName : "",
920                         useNick ? "\"" : "",
921                         v1 ? " -firstProtocolVersion 1" : "",
922                         hasBook ? "" : " -fNoOwnBookUCI",
923                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
924                         storeVariant ? " -variant " : "",
925                         storeVariant ? VariantName(gameInfo.variant) : "");
926         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
927         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
928         if(q)   free(q);
929     }
930     ReplaceEngine(cps, i);
931 }
932
933 void
934 InitTimeControls ()
935 {
936     int matched, min, sec;
937     /*
938      * Parse timeControl resource
939      */
940     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
941                           appData.movesPerSession)) {
942         char buf[MSG_SIZ];
943         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
944         DisplayFatalError(buf, 0, 2);
945     }
946
947     /*
948      * Parse searchTime resource
949      */
950     if (*appData.searchTime != NULLCHAR) {
951         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
952         if (matched == 1) {
953             searchTime = min * 60;
954         } else if (matched == 2) {
955             searchTime = min * 60 + sec;
956         } else {
957             char buf[MSG_SIZ];
958             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
959             DisplayFatalError(buf, 0, 2);
960         }
961     }
962 }
963
964 void
965 InitBackEnd1 ()
966 {
967
968     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
969     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
970
971     GetTimeMark(&programStartTime);
972     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
973     appData.seedBase = random() + (random()<<15);
974     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
975
976     ClearProgramStats();
977     programStats.ok_to_send = 1;
978     programStats.seen_stat = 0;
979
980     /*
981      * Initialize game list
982      */
983     ListNew(&gameList);
984
985
986     /*
987      * Internet chess server status
988      */
989     if (appData.icsActive) {
990         appData.matchMode = FALSE;
991         appData.matchGames = 0;
992 #if ZIPPY
993         appData.noChessProgram = !appData.zippyPlay;
994 #else
995         appData.zippyPlay = FALSE;
996         appData.zippyTalk = FALSE;
997         appData.noChessProgram = TRUE;
998 #endif
999         if (*appData.icsHelper != NULLCHAR) {
1000             appData.useTelnet = TRUE;
1001             appData.telnetProgram = appData.icsHelper;
1002         }
1003     } else {
1004         appData.zippyTalk = appData.zippyPlay = FALSE;
1005     }
1006
1007     /* [AS] Initialize pv info list [HGM] and game state */
1008     {
1009         int i, j;
1010
1011         for( i=0; i<=framePtr; i++ ) {
1012             pvInfoList[i].depth = -1;
1013             boards[i][EP_STATUS] = EP_NONE;
1014             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1015         }
1016     }
1017
1018     InitTimeControls();
1019
1020     /* [AS] Adjudication threshold */
1021     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1022
1023     InitEngine(&first, 0);
1024     InitEngine(&second, 1);
1025     CommonEngineInit();
1026
1027     pairing.which = "pairing"; // pairing engine
1028     pairing.pr = NoProc;
1029     pairing.isr = NULL;
1030     pairing.program = appData.pairingEngine;
1031     pairing.host = "localhost";
1032     pairing.dir = ".";
1033
1034     if (appData.icsActive) {
1035         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1036     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1037         appData.clockMode = FALSE;
1038         first.sendTime = second.sendTime = 0;
1039     }
1040
1041 #if ZIPPY
1042     /* Override some settings from environment variables, for backward
1043        compatibility.  Unfortunately it's not feasible to have the env
1044        vars just set defaults, at least in xboard.  Ugh.
1045     */
1046     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1047       ZippyInit();
1048     }
1049 #endif
1050
1051     if (!appData.icsActive) {
1052       char buf[MSG_SIZ];
1053       int len;
1054
1055       /* Check for variants that are supported only in ICS mode,
1056          or not at all.  Some that are accepted here nevertheless
1057          have bugs; see comments below.
1058       */
1059       VariantClass variant = StringToVariant(appData.variant);
1060       switch (variant) {
1061       case VariantBughouse:     /* need four players and two boards */
1062       case VariantKriegspiel:   /* need to hide pieces and move details */
1063         /* case VariantFischeRandom: (Fabien: moved below) */
1064         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1065         if( (len >= MSG_SIZ) && appData.debugMode )
1066           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1067
1068         DisplayFatalError(buf, 0, 2);
1069         return;
1070
1071       case VariantUnknown:
1072       case VariantLoadable:
1073       case Variant29:
1074       case Variant30:
1075       case Variant31:
1076       case Variant32:
1077       case Variant33:
1078       case Variant34:
1079       case Variant35:
1080       case Variant36:
1081       default:
1082         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1083         if( (len >= MSG_SIZ) && appData.debugMode )
1084           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1085
1086         DisplayFatalError(buf, 0, 2);
1087         return;
1088
1089       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1090       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1091       case VariantGothic:     /* [HGM] should work */
1092       case VariantCapablanca: /* [HGM] should work */
1093       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1094       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1095       case VariantKnightmate: /* [HGM] should work */
1096       case VariantCylinder:   /* [HGM] untested */
1097       case VariantFalcon:     /* [HGM] untested */
1098       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1099                                  offboard interposition not understood */
1100       case VariantNormal:     /* definitely works! */
1101       case VariantWildCastle: /* pieces not automatically shuffled */
1102       case VariantNoCastle:   /* pieces not automatically shuffled */
1103       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1104       case VariantLosers:     /* should work except for win condition,
1105                                  and doesn't know captures are mandatory */
1106       case VariantSuicide:    /* should work except for win condition,
1107                                  and doesn't know captures are mandatory */
1108       case VariantGiveaway:   /* should work except for win condition,
1109                                  and doesn't know captures are mandatory */
1110       case VariantTwoKings:   /* should work */
1111       case VariantAtomic:     /* should work except for win condition */
1112       case Variant3Check:     /* should work except for win condition */
1113       case VariantShatranj:   /* should work except for all win conditions */
1114       case VariantMakruk:     /* should work except for draw countdown */
1115       case VariantBerolina:   /* might work if TestLegality is off */
1116       case VariantCapaRandom: /* should work */
1117       case VariantJanus:      /* should work */
1118       case VariantSuper:      /* experimental */
1119       case VariantGreat:      /* experimental, requires legality testing to be off */
1120       case VariantSChess:     /* S-Chess, should work */
1121       case VariantGrand:      /* should work */
1122       case VariantSpartan:    /* should work */
1123         break;
1124       }
1125     }
1126
1127 }
1128
1129 int
1130 NextIntegerFromString (char ** str, long * value)
1131 {
1132     int result = -1;
1133     char * s = *str;
1134
1135     while( *s == ' ' || *s == '\t' ) {
1136         s++;
1137     }
1138
1139     *value = 0;
1140
1141     if( *s >= '0' && *s <= '9' ) {
1142         while( *s >= '0' && *s <= '9' ) {
1143             *value = *value * 10 + (*s - '0');
1144             s++;
1145         }
1146
1147         result = 0;
1148     }
1149
1150     *str = s;
1151
1152     return result;
1153 }
1154
1155 int
1156 NextTimeControlFromString (char ** str, long * value)
1157 {
1158     long temp;
1159     int result = NextIntegerFromString( str, &temp );
1160
1161     if( result == 0 ) {
1162         *value = temp * 60; /* Minutes */
1163         if( **str == ':' ) {
1164             (*str)++;
1165             result = NextIntegerFromString( str, &temp );
1166             *value += temp; /* Seconds */
1167         }
1168     }
1169
1170     return result;
1171 }
1172
1173 int
1174 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1175 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1176     int result = -1, type = 0; long temp, temp2;
1177
1178     if(**str != ':') return -1; // old params remain in force!
1179     (*str)++;
1180     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1181     if( NextIntegerFromString( str, &temp ) ) return -1;
1182     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1183
1184     if(**str != '/') {
1185         /* time only: incremental or sudden-death time control */
1186         if(**str == '+') { /* increment follows; read it */
1187             (*str)++;
1188             if(**str == '!') type = *(*str)++; // Bronstein TC
1189             if(result = NextIntegerFromString( str, &temp2)) return -1;
1190             *inc = temp2 * 1000;
1191             if(**str == '.') { // read fraction of increment
1192                 char *start = ++(*str);
1193                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1194                 temp2 *= 1000;
1195                 while(start++ < *str) temp2 /= 10;
1196                 *inc += temp2;
1197             }
1198         } else *inc = 0;
1199         *moves = 0; *tc = temp * 1000; *incType = type;
1200         return 0;
1201     }
1202
1203     (*str)++; /* classical time control */
1204     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1205
1206     if(result == 0) {
1207         *moves = temp;
1208         *tc    = temp2 * 1000;
1209         *inc   = 0;
1210         *incType = type;
1211     }
1212     return result;
1213 }
1214
1215 int
1216 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1217 {   /* [HGM] get time to add from the multi-session time-control string */
1218     int incType, moves=1; /* kludge to force reading of first session */
1219     long time, increment;
1220     char *s = tcString;
1221
1222     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1223     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1224     do {
1225         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1226         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1227         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1228         if(movenr == -1) return time;    /* last move before new session     */
1229         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1230         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1231         if(!moves) return increment;     /* current session is incremental   */
1232         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1233     } while(movenr >= -1);               /* try again for next session       */
1234
1235     return 0; // no new time quota on this move
1236 }
1237
1238 int
1239 ParseTimeControl (char *tc, float ti, int mps)
1240 {
1241   long tc1;
1242   long tc2;
1243   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1244   int min, sec=0;
1245
1246   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1247   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1248       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1249   if(ti > 0) {
1250
1251     if(mps)
1252       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1253     else 
1254       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1255   } else {
1256     if(mps)
1257       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1258     else 
1259       snprintf(buf, MSG_SIZ, ":%s", mytc);
1260   }
1261   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1262   
1263   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1264     return FALSE;
1265   }
1266
1267   if( *tc == '/' ) {
1268     /* Parse second time control */
1269     tc++;
1270
1271     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1272       return FALSE;
1273     }
1274
1275     if( tc2 == 0 ) {
1276       return FALSE;
1277     }
1278
1279     timeControl_2 = tc2 * 1000;
1280   }
1281   else {
1282     timeControl_2 = 0;
1283   }
1284
1285   if( tc1 == 0 ) {
1286     return FALSE;
1287   }
1288
1289   timeControl = tc1 * 1000;
1290
1291   if (ti >= 0) {
1292     timeIncrement = ti * 1000;  /* convert to ms */
1293     movesPerSession = 0;
1294   } else {
1295     timeIncrement = 0;
1296     movesPerSession = mps;
1297   }
1298   return TRUE;
1299 }
1300
1301 void
1302 InitBackEnd2 ()
1303 {
1304     if (appData.debugMode) {
1305         fprintf(debugFP, "%s\n", programVersion);
1306     }
1307
1308     set_cont_sequence(appData.wrapContSeq);
1309     if (appData.matchGames > 0) {
1310         appData.matchMode = TRUE;
1311     } else if (appData.matchMode) {
1312         appData.matchGames = 1;
1313     }
1314     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1315         appData.matchGames = appData.sameColorGames;
1316     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1317         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1318         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1319     }
1320     Reset(TRUE, FALSE);
1321     if (appData.noChessProgram || first.protocolVersion == 1) {
1322       InitBackEnd3();
1323     } else {
1324       /* kludge: allow timeout for initial "feature" commands */
1325       FreezeUI();
1326       DisplayMessage("", _("Starting chess program"));
1327       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1328     }
1329 }
1330
1331 int
1332 CalculateIndex (int index, int gameNr)
1333 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1334     int res;
1335     if(index > 0) return index; // fixed nmber
1336     if(index == 0) return 1;
1337     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1338     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1339     return res;
1340 }
1341
1342 int
1343 LoadGameOrPosition (int gameNr)
1344 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1345     if (*appData.loadGameFile != NULLCHAR) {
1346         if (!LoadGameFromFile(appData.loadGameFile,
1347                 CalculateIndex(appData.loadGameIndex, gameNr),
1348                               appData.loadGameFile, FALSE)) {
1349             DisplayFatalError(_("Bad game file"), 0, 1);
1350             return 0;
1351         }
1352     } else if (*appData.loadPositionFile != NULLCHAR) {
1353         if (!LoadPositionFromFile(appData.loadPositionFile,
1354                 CalculateIndex(appData.loadPositionIndex, gameNr),
1355                                   appData.loadPositionFile)) {
1356             DisplayFatalError(_("Bad position file"), 0, 1);
1357             return 0;
1358         }
1359     }
1360     return 1;
1361 }
1362
1363 void
1364 ReserveGame (int gameNr, char resChar)
1365 {
1366     FILE *tf = fopen(appData.tourneyFile, "r+");
1367     char *p, *q, c, buf[MSG_SIZ];
1368     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1369     safeStrCpy(buf, lastMsg, MSG_SIZ);
1370     DisplayMessage(_("Pick new game"), "");
1371     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1372     ParseArgsFromFile(tf);
1373     p = q = appData.results;
1374     if(appData.debugMode) {
1375       char *r = appData.participants;
1376       fprintf(debugFP, "results = '%s'\n", p);
1377       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1378       fprintf(debugFP, "\n");
1379     }
1380     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1381     nextGame = q - p;
1382     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1383     safeStrCpy(q, p, strlen(p) + 2);
1384     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1385     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1386     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1387         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1388         q[nextGame] = '*';
1389     }
1390     fseek(tf, -(strlen(p)+4), SEEK_END);
1391     c = fgetc(tf);
1392     if(c != '"') // depending on DOS or Unix line endings we can be one off
1393          fseek(tf, -(strlen(p)+2), SEEK_END);
1394     else fseek(tf, -(strlen(p)+3), SEEK_END);
1395     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1396     DisplayMessage(buf, "");
1397     free(p); appData.results = q;
1398     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1399        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1400         UnloadEngine(&first);  // next game belongs to other pairing;
1401         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1402     }
1403 }
1404
1405 void
1406 MatchEvent (int mode)
1407 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1408         int dummy;
1409         if(matchMode) { // already in match mode: switch it off
1410             abortMatch = TRUE;
1411             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1412             return;
1413         }
1414 //      if(gameMode != BeginningOfGame) {
1415 //          DisplayError(_("You can only start a match from the initial position."), 0);
1416 //          return;
1417 //      }
1418         abortMatch = FALSE;
1419         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1420         /* Set up machine vs. machine match */
1421         nextGame = 0;
1422         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1423         if(appData.tourneyFile[0]) {
1424             ReserveGame(-1, 0);
1425             if(nextGame > appData.matchGames) {
1426                 char buf[MSG_SIZ];
1427                 if(strchr(appData.results, '*') == NULL) {
1428                     FILE *f;
1429                     appData.tourneyCycles++;
1430                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1431                         fclose(f);
1432                         NextTourneyGame(-1, &dummy);
1433                         ReserveGame(-1, 0);
1434                         if(nextGame <= appData.matchGames) {
1435                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1436                             matchMode = mode;
1437                             ScheduleDelayedEvent(NextMatchGame, 10000);
1438                             return;
1439                         }
1440                     }
1441                 }
1442                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1443                 DisplayError(buf, 0);
1444                 appData.tourneyFile[0] = 0;
1445                 return;
1446             }
1447         } else
1448         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1449             DisplayFatalError(_("Can't have a match with no chess programs"),
1450                               0, 2);
1451             return;
1452         }
1453         matchMode = mode;
1454         matchGame = roundNr = 1;
1455         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1456         NextMatchGame();
1457 }
1458
1459 void
1460 InitBackEnd3 P((void))
1461 {
1462     GameMode initialMode;
1463     char buf[MSG_SIZ];
1464     int err, len;
1465
1466     InitChessProgram(&first, startedFromSetupPosition);
1467
1468     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1469         free(programVersion);
1470         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1471         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1472     }
1473
1474     if (appData.icsActive) {
1475 #ifdef WIN32
1476         /* [DM] Make a console window if needed [HGM] merged ifs */
1477         ConsoleCreate();
1478 #endif
1479         err = establish();
1480         if (err != 0)
1481           {
1482             if (*appData.icsCommPort != NULLCHAR)
1483               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1484                              appData.icsCommPort);
1485             else
1486               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1487                         appData.icsHost, appData.icsPort);
1488
1489             if( (len >= MSG_SIZ) && appData.debugMode )
1490               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1491
1492             DisplayFatalError(buf, err, 1);
1493             return;
1494         }
1495         SetICSMode();
1496         telnetISR =
1497           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1498         fromUserISR =
1499           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1500         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1501             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1502     } else if (appData.noChessProgram) {
1503         SetNCPMode();
1504     } else {
1505         SetGNUMode();
1506     }
1507
1508     if (*appData.cmailGameName != NULLCHAR) {
1509         SetCmailMode();
1510         OpenLoopback(&cmailPR);
1511         cmailISR =
1512           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1513     }
1514
1515     ThawUI();
1516     DisplayMessage("", "");
1517     if (StrCaseCmp(appData.initialMode, "") == 0) {
1518       initialMode = BeginningOfGame;
1519       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1520         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1521         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1522         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1523         ModeHighlight();
1524       }
1525     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1526       initialMode = TwoMachinesPlay;
1527     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1528       initialMode = AnalyzeFile;
1529     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1530       initialMode = AnalyzeMode;
1531     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1532       initialMode = MachinePlaysWhite;
1533     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1534       initialMode = MachinePlaysBlack;
1535     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1536       initialMode = EditGame;
1537     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1538       initialMode = EditPosition;
1539     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1540       initialMode = Training;
1541     } else {
1542       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1543       if( (len >= MSG_SIZ) && appData.debugMode )
1544         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1545
1546       DisplayFatalError(buf, 0, 2);
1547       return;
1548     }
1549
1550     if (appData.matchMode) {
1551         if(appData.tourneyFile[0]) { // start tourney from command line
1552             FILE *f;
1553             if(f = fopen(appData.tourneyFile, "r")) {
1554                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1555                 fclose(f);
1556                 appData.clockMode = TRUE;
1557                 SetGNUMode();
1558             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1559         }
1560         MatchEvent(TRUE);
1561     } else if (*appData.cmailGameName != NULLCHAR) {
1562         /* Set up cmail mode */
1563         ReloadCmailMsgEvent(TRUE);
1564     } else {
1565         /* Set up other modes */
1566         if (initialMode == AnalyzeFile) {
1567           if (*appData.loadGameFile == NULLCHAR) {
1568             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1569             return;
1570           }
1571         }
1572         if (*appData.loadGameFile != NULLCHAR) {
1573             (void) LoadGameFromFile(appData.loadGameFile,
1574                                     appData.loadGameIndex,
1575                                     appData.loadGameFile, TRUE);
1576         } else if (*appData.loadPositionFile != NULLCHAR) {
1577             (void) LoadPositionFromFile(appData.loadPositionFile,
1578                                         appData.loadPositionIndex,
1579                                         appData.loadPositionFile);
1580             /* [HGM] try to make self-starting even after FEN load */
1581             /* to allow automatic setup of fairy variants with wtm */
1582             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1583                 gameMode = BeginningOfGame;
1584                 setboardSpoiledMachineBlack = 1;
1585             }
1586             /* [HGM] loadPos: make that every new game uses the setup */
1587             /* from file as long as we do not switch variant          */
1588             if(!blackPlaysFirst) {
1589                 startedFromPositionFile = TRUE;
1590                 CopyBoard(filePosition, boards[0]);
1591             }
1592         }
1593         if (initialMode == AnalyzeMode) {
1594           if (appData.noChessProgram) {
1595             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1596             return;
1597           }
1598           if (appData.icsActive) {
1599             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1600             return;
1601           }
1602           AnalyzeModeEvent();
1603         } else if (initialMode == AnalyzeFile) {
1604           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1605           ShowThinkingEvent();
1606           AnalyzeFileEvent();
1607           AnalysisPeriodicEvent(1);
1608         } else if (initialMode == MachinePlaysWhite) {
1609           if (appData.noChessProgram) {
1610             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1611                               0, 2);
1612             return;
1613           }
1614           if (appData.icsActive) {
1615             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1616                               0, 2);
1617             return;
1618           }
1619           MachineWhiteEvent();
1620         } else if (initialMode == MachinePlaysBlack) {
1621           if (appData.noChessProgram) {
1622             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1623                               0, 2);
1624             return;
1625           }
1626           if (appData.icsActive) {
1627             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1628                               0, 2);
1629             return;
1630           }
1631           MachineBlackEvent();
1632         } else if (initialMode == TwoMachinesPlay) {
1633           if (appData.noChessProgram) {
1634             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1635                               0, 2);
1636             return;
1637           }
1638           if (appData.icsActive) {
1639             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1640                               0, 2);
1641             return;
1642           }
1643           TwoMachinesEvent();
1644         } else if (initialMode == EditGame) {
1645           EditGameEvent();
1646         } else if (initialMode == EditPosition) {
1647           EditPositionEvent();
1648         } else if (initialMode == Training) {
1649           if (*appData.loadGameFile == NULLCHAR) {
1650             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1651             return;
1652           }
1653           TrainingEvent();
1654         }
1655     }
1656 }
1657
1658 void
1659 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1660 {
1661     DisplayBook(current+1);
1662
1663     MoveHistorySet( movelist, first, last, current, pvInfoList );
1664
1665     EvalGraphSet( first, last, current, pvInfoList );
1666
1667     MakeEngineOutputTitle();
1668 }
1669
1670 /*
1671  * Establish will establish a contact to a remote host.port.
1672  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1673  *  used to talk to the host.
1674  * Returns 0 if okay, error code if not.
1675  */
1676 int
1677 establish ()
1678 {
1679     char buf[MSG_SIZ];
1680
1681     if (*appData.icsCommPort != NULLCHAR) {
1682         /* Talk to the host through a serial comm port */
1683         return OpenCommPort(appData.icsCommPort, &icsPR);
1684
1685     } else if (*appData.gateway != NULLCHAR) {
1686         if (*appData.remoteShell == NULLCHAR) {
1687             /* Use the rcmd protocol to run telnet program on a gateway host */
1688             snprintf(buf, sizeof(buf), "%s %s %s",
1689                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1690             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1691
1692         } else {
1693             /* Use the rsh program to run telnet program on a gateway host */
1694             if (*appData.remoteUser == NULLCHAR) {
1695                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1696                         appData.gateway, appData.telnetProgram,
1697                         appData.icsHost, appData.icsPort);
1698             } else {
1699                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1700                         appData.remoteShell, appData.gateway,
1701                         appData.remoteUser, appData.telnetProgram,
1702                         appData.icsHost, appData.icsPort);
1703             }
1704             return StartChildProcess(buf, "", &icsPR);
1705
1706         }
1707     } else if (appData.useTelnet) {
1708         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1709
1710     } else {
1711         /* TCP socket interface differs somewhat between
1712            Unix and NT; handle details in the front end.
1713            */
1714         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1715     }
1716 }
1717
1718 void
1719 EscapeExpand (char *p, char *q)
1720 {       // [HGM] initstring: routine to shape up string arguments
1721         while(*p++ = *q++) if(p[-1] == '\\')
1722             switch(*q++) {
1723                 case 'n': p[-1] = '\n'; break;
1724                 case 'r': p[-1] = '\r'; break;
1725                 case 't': p[-1] = '\t'; break;
1726                 case '\\': p[-1] = '\\'; break;
1727                 case 0: *p = 0; return;
1728                 default: p[-1] = q[-1]; break;
1729             }
1730 }
1731
1732 void
1733 show_bytes (FILE *fp, char *buf, int count)
1734 {
1735     while (count--) {
1736         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1737             fprintf(fp, "\\%03o", *buf & 0xff);
1738         } else {
1739             putc(*buf, fp);
1740         }
1741         buf++;
1742     }
1743     fflush(fp);
1744 }
1745
1746 /* Returns an errno value */
1747 int
1748 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1749 {
1750     char buf[8192], *p, *q, *buflim;
1751     int left, newcount, outcount;
1752
1753     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1754         *appData.gateway != NULLCHAR) {
1755         if (appData.debugMode) {
1756             fprintf(debugFP, ">ICS: ");
1757             show_bytes(debugFP, message, count);
1758             fprintf(debugFP, "\n");
1759         }
1760         return OutputToProcess(pr, message, count, outError);
1761     }
1762
1763     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1764     p = message;
1765     q = buf;
1766     left = count;
1767     newcount = 0;
1768     while (left) {
1769         if (q >= buflim) {
1770             if (appData.debugMode) {
1771                 fprintf(debugFP, ">ICS: ");
1772                 show_bytes(debugFP, buf, newcount);
1773                 fprintf(debugFP, "\n");
1774             }
1775             outcount = OutputToProcess(pr, buf, newcount, outError);
1776             if (outcount < newcount) return -1; /* to be sure */
1777             q = buf;
1778             newcount = 0;
1779         }
1780         if (*p == '\n') {
1781             *q++ = '\r';
1782             newcount++;
1783         } else if (((unsigned char) *p) == TN_IAC) {
1784             *q++ = (char) TN_IAC;
1785             newcount ++;
1786         }
1787         *q++ = *p++;
1788         newcount++;
1789         left--;
1790     }
1791     if (appData.debugMode) {
1792         fprintf(debugFP, ">ICS: ");
1793         show_bytes(debugFP, buf, newcount);
1794         fprintf(debugFP, "\n");
1795     }
1796     outcount = OutputToProcess(pr, buf, newcount, outError);
1797     if (outcount < newcount) return -1; /* to be sure */
1798     return count;
1799 }
1800
1801 void
1802 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1803 {
1804     int outError, outCount;
1805     static int gotEof = 0;
1806
1807     /* Pass data read from player on to ICS */
1808     if (count > 0) {
1809         gotEof = 0;
1810         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1811         if (outCount < count) {
1812             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1813         }
1814     } else if (count < 0) {
1815         RemoveInputSource(isr);
1816         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1817     } else if (gotEof++ > 0) {
1818         RemoveInputSource(isr);
1819         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1820     }
1821 }
1822
1823 void
1824 KeepAlive ()
1825 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1826     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1827     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1828     SendToICS("date\n");
1829     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1830 }
1831
1832 /* added routine for printf style output to ics */
1833 void
1834 ics_printf (char *format, ...)
1835 {
1836     char buffer[MSG_SIZ];
1837     va_list args;
1838
1839     va_start(args, format);
1840     vsnprintf(buffer, sizeof(buffer), format, args);
1841     buffer[sizeof(buffer)-1] = '\0';
1842     SendToICS(buffer);
1843     va_end(args);
1844 }
1845
1846 void
1847 SendToICS (char *s)
1848 {
1849     int count, outCount, outError;
1850
1851     if (icsPR == NoProc) return;
1852
1853     count = strlen(s);
1854     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1855     if (outCount < count) {
1856         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1857     }
1858 }
1859
1860 /* This is used for sending logon scripts to the ICS. Sending
1861    without a delay causes problems when using timestamp on ICC
1862    (at least on my machine). */
1863 void
1864 SendToICSDelayed (char *s, long msdelay)
1865 {
1866     int count, outCount, outError;
1867
1868     if (icsPR == NoProc) return;
1869
1870     count = strlen(s);
1871     if (appData.debugMode) {
1872         fprintf(debugFP, ">ICS: ");
1873         show_bytes(debugFP, s, count);
1874         fprintf(debugFP, "\n");
1875     }
1876     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1877                                       msdelay);
1878     if (outCount < count) {
1879         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1880     }
1881 }
1882
1883
1884 /* Remove all highlighting escape sequences in s
1885    Also deletes any suffix starting with '('
1886    */
1887 char *
1888 StripHighlightAndTitle (char *s)
1889 {
1890     static char retbuf[MSG_SIZ];
1891     char *p = retbuf;
1892
1893     while (*s != NULLCHAR) {
1894         while (*s == '\033') {
1895             while (*s != NULLCHAR && !isalpha(*s)) s++;
1896             if (*s != NULLCHAR) s++;
1897         }
1898         while (*s != NULLCHAR && *s != '\033') {
1899             if (*s == '(' || *s == '[') {
1900                 *p = NULLCHAR;
1901                 return retbuf;
1902             }
1903             *p++ = *s++;
1904         }
1905     }
1906     *p = NULLCHAR;
1907     return retbuf;
1908 }
1909
1910 /* Remove all highlighting escape sequences in s */
1911 char *
1912 StripHighlight (char *s)
1913 {
1914     static char retbuf[MSG_SIZ];
1915     char *p = retbuf;
1916
1917     while (*s != NULLCHAR) {
1918         while (*s == '\033') {
1919             while (*s != NULLCHAR && !isalpha(*s)) s++;
1920             if (*s != NULLCHAR) s++;
1921         }
1922         while (*s != NULLCHAR && *s != '\033') {
1923             *p++ = *s++;
1924         }
1925     }
1926     *p = NULLCHAR;
1927     return retbuf;
1928 }
1929
1930 char *variantNames[] = VARIANT_NAMES;
1931 char *
1932 VariantName (VariantClass v)
1933 {
1934     return variantNames[v];
1935 }
1936
1937
1938 /* Identify a variant from the strings the chess servers use or the
1939    PGN Variant tag names we use. */
1940 VariantClass
1941 StringToVariant (char *e)
1942 {
1943     char *p;
1944     int wnum = -1;
1945     VariantClass v = VariantNormal;
1946     int i, found = FALSE;
1947     char buf[MSG_SIZ];
1948     int len;
1949
1950     if (!e) return v;
1951
1952     /* [HGM] skip over optional board-size prefixes */
1953     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1954         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1955         while( *e++ != '_');
1956     }
1957
1958     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1959         v = VariantNormal;
1960         found = TRUE;
1961     } else
1962     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1963       if (StrCaseStr(e, variantNames[i])) {
1964         v = (VariantClass) i;
1965         found = TRUE;
1966         break;
1967       }
1968     }
1969
1970     if (!found) {
1971       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1972           || StrCaseStr(e, "wild/fr")
1973           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1974         v = VariantFischeRandom;
1975       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1976                  (i = 1, p = StrCaseStr(e, "w"))) {
1977         p += i;
1978         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1979         if (isdigit(*p)) {
1980           wnum = atoi(p);
1981         } else {
1982           wnum = -1;
1983         }
1984         switch (wnum) {
1985         case 0: /* FICS only, actually */
1986         case 1:
1987           /* Castling legal even if K starts on d-file */
1988           v = VariantWildCastle;
1989           break;
1990         case 2:
1991         case 3:
1992         case 4:
1993           /* Castling illegal even if K & R happen to start in
1994              normal positions. */
1995           v = VariantNoCastle;
1996           break;
1997         case 5:
1998         case 7:
1999         case 8:
2000         case 10:
2001         case 11:
2002         case 12:
2003         case 13:
2004         case 14:
2005         case 15:
2006         case 18:
2007         case 19:
2008           /* Castling legal iff K & R start in normal positions */
2009           v = VariantNormal;
2010           break;
2011         case 6:
2012         case 20:
2013         case 21:
2014           /* Special wilds for position setup; unclear what to do here */
2015           v = VariantLoadable;
2016           break;
2017         case 9:
2018           /* Bizarre ICC game */
2019           v = VariantTwoKings;
2020           break;
2021         case 16:
2022           v = VariantKriegspiel;
2023           break;
2024         case 17:
2025           v = VariantLosers;
2026           break;
2027         case 22:
2028           v = VariantFischeRandom;
2029           break;
2030         case 23:
2031           v = VariantCrazyhouse;
2032           break;
2033         case 24:
2034           v = VariantBughouse;
2035           break;
2036         case 25:
2037           v = Variant3Check;
2038           break;
2039         case 26:
2040           /* Not quite the same as FICS suicide! */
2041           v = VariantGiveaway;
2042           break;
2043         case 27:
2044           v = VariantAtomic;
2045           break;
2046         case 28:
2047           v = VariantShatranj;
2048           break;
2049
2050         /* Temporary names for future ICC types.  The name *will* change in
2051            the next xboard/WinBoard release after ICC defines it. */
2052         case 29:
2053           v = Variant29;
2054           break;
2055         case 30:
2056           v = Variant30;
2057           break;
2058         case 31:
2059           v = Variant31;
2060           break;
2061         case 32:
2062           v = Variant32;
2063           break;
2064         case 33:
2065           v = Variant33;
2066           break;
2067         case 34:
2068           v = Variant34;
2069           break;
2070         case 35:
2071           v = Variant35;
2072           break;
2073         case 36:
2074           v = Variant36;
2075           break;
2076         case 37:
2077           v = VariantShogi;
2078           break;
2079         case 38:
2080           v = VariantXiangqi;
2081           break;
2082         case 39:
2083           v = VariantCourier;
2084           break;
2085         case 40:
2086           v = VariantGothic;
2087           break;
2088         case 41:
2089           v = VariantCapablanca;
2090           break;
2091         case 42:
2092           v = VariantKnightmate;
2093           break;
2094         case 43:
2095           v = VariantFairy;
2096           break;
2097         case 44:
2098           v = VariantCylinder;
2099           break;
2100         case 45:
2101           v = VariantFalcon;
2102           break;
2103         case 46:
2104           v = VariantCapaRandom;
2105           break;
2106         case 47:
2107           v = VariantBerolina;
2108           break;
2109         case 48:
2110           v = VariantJanus;
2111           break;
2112         case 49:
2113           v = VariantSuper;
2114           break;
2115         case 50:
2116           v = VariantGreat;
2117           break;
2118         case -1:
2119           /* Found "wild" or "w" in the string but no number;
2120              must assume it's normal chess. */
2121           v = VariantNormal;
2122           break;
2123         default:
2124           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2125           if( (len >= MSG_SIZ) && appData.debugMode )
2126             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2127
2128           DisplayError(buf, 0);
2129           v = VariantUnknown;
2130           break;
2131         }
2132       }
2133     }
2134     if (appData.debugMode) {
2135       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2136               e, wnum, VariantName(v));
2137     }
2138     return v;
2139 }
2140
2141 static int leftover_start = 0, leftover_len = 0;
2142 char star_match[STAR_MATCH_N][MSG_SIZ];
2143
2144 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2145    advance *index beyond it, and set leftover_start to the new value of
2146    *index; else return FALSE.  If pattern contains the character '*', it
2147    matches any sequence of characters not containing '\r', '\n', or the
2148    character following the '*' (if any), and the matched sequence(s) are
2149    copied into star_match.
2150    */
2151 int
2152 looking_at ( char *buf, int *index, char *pattern)
2153 {
2154     char *bufp = &buf[*index], *patternp = pattern;
2155     int star_count = 0;
2156     char *matchp = star_match[0];
2157
2158     for (;;) {
2159         if (*patternp == NULLCHAR) {
2160             *index = leftover_start = bufp - buf;
2161             *matchp = NULLCHAR;
2162             return TRUE;
2163         }
2164         if (*bufp == NULLCHAR) return FALSE;
2165         if (*patternp == '*') {
2166             if (*bufp == *(patternp + 1)) {
2167                 *matchp = NULLCHAR;
2168                 matchp = star_match[++star_count];
2169                 patternp += 2;
2170                 bufp++;
2171                 continue;
2172             } else if (*bufp == '\n' || *bufp == '\r') {
2173                 patternp++;
2174                 if (*patternp == NULLCHAR)
2175                   continue;
2176                 else
2177                   return FALSE;
2178             } else {
2179                 *matchp++ = *bufp++;
2180                 continue;
2181             }
2182         }
2183         if (*patternp != *bufp) return FALSE;
2184         patternp++;
2185         bufp++;
2186     }
2187 }
2188
2189 void
2190 SendToPlayer (char *data, int length)
2191 {
2192     int error, outCount;
2193     outCount = OutputToProcess(NoProc, data, length, &error);
2194     if (outCount < length) {
2195         DisplayFatalError(_("Error writing to display"), error, 1);
2196     }
2197 }
2198
2199 void
2200 PackHolding (char packed[], char *holding)
2201 {
2202     char *p = holding;
2203     char *q = packed;
2204     int runlength = 0;
2205     int curr = 9999;
2206     do {
2207         if (*p == curr) {
2208             runlength++;
2209         } else {
2210             switch (runlength) {
2211               case 0:
2212                 break;
2213               case 1:
2214                 *q++ = curr;
2215                 break;
2216               case 2:
2217                 *q++ = curr;
2218                 *q++ = curr;
2219                 break;
2220               default:
2221                 sprintf(q, "%d", runlength);
2222                 while (*q) q++;
2223                 *q++ = curr;
2224                 break;
2225             }
2226             runlength = 1;
2227             curr = *p;
2228         }
2229     } while (*p++);
2230     *q = NULLCHAR;
2231 }
2232
2233 /* Telnet protocol requests from the front end */
2234 void
2235 TelnetRequest (unsigned char ddww, unsigned char option)
2236 {
2237     unsigned char msg[3];
2238     int outCount, outError;
2239
2240     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2241
2242     if (appData.debugMode) {
2243         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2244         switch (ddww) {
2245           case TN_DO:
2246             ddwwStr = "DO";
2247             break;
2248           case TN_DONT:
2249             ddwwStr = "DONT";
2250             break;
2251           case TN_WILL:
2252             ddwwStr = "WILL";
2253             break;
2254           case TN_WONT:
2255             ddwwStr = "WONT";
2256             break;
2257           default:
2258             ddwwStr = buf1;
2259             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2260             break;
2261         }
2262         switch (option) {
2263           case TN_ECHO:
2264             optionStr = "ECHO";
2265             break;
2266           default:
2267             optionStr = buf2;
2268             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2269             break;
2270         }
2271         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2272     }
2273     msg[0] = TN_IAC;
2274     msg[1] = ddww;
2275     msg[2] = option;
2276     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2277     if (outCount < 3) {
2278         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2279     }
2280 }
2281
2282 void
2283 DoEcho ()
2284 {
2285     if (!appData.icsActive) return;
2286     TelnetRequest(TN_DO, TN_ECHO);
2287 }
2288
2289 void
2290 DontEcho ()
2291 {
2292     if (!appData.icsActive) return;
2293     TelnetRequest(TN_DONT, TN_ECHO);
2294 }
2295
2296 void
2297 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2298 {
2299     /* put the holdings sent to us by the server on the board holdings area */
2300     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2301     char p;
2302     ChessSquare piece;
2303
2304     if(gameInfo.holdingsWidth < 2)  return;
2305     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2306         return; // prevent overwriting by pre-board holdings
2307
2308     if( (int)lowestPiece >= BlackPawn ) {
2309         holdingsColumn = 0;
2310         countsColumn = 1;
2311         holdingsStartRow = BOARD_HEIGHT-1;
2312         direction = -1;
2313     } else {
2314         holdingsColumn = BOARD_WIDTH-1;
2315         countsColumn = BOARD_WIDTH-2;
2316         holdingsStartRow = 0;
2317         direction = 1;
2318     }
2319
2320     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2321         board[i][holdingsColumn] = EmptySquare;
2322         board[i][countsColumn]   = (ChessSquare) 0;
2323     }
2324     while( (p=*holdings++) != NULLCHAR ) {
2325         piece = CharToPiece( ToUpper(p) );
2326         if(piece == EmptySquare) continue;
2327         /*j = (int) piece - (int) WhitePawn;*/
2328         j = PieceToNumber(piece);
2329         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2330         if(j < 0) continue;               /* should not happen */
2331         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2332         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2333         board[holdingsStartRow+j*direction][countsColumn]++;
2334     }
2335 }
2336
2337
2338 void
2339 VariantSwitch (Board board, VariantClass newVariant)
2340 {
2341    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2342    static Board oldBoard;
2343
2344    startedFromPositionFile = FALSE;
2345    if(gameInfo.variant == newVariant) return;
2346
2347    /* [HGM] This routine is called each time an assignment is made to
2348     * gameInfo.variant during a game, to make sure the board sizes
2349     * are set to match the new variant. If that means adding or deleting
2350     * holdings, we shift the playing board accordingly
2351     * This kludge is needed because in ICS observe mode, we get boards
2352     * of an ongoing game without knowing the variant, and learn about the
2353     * latter only later. This can be because of the move list we requested,
2354     * in which case the game history is refilled from the beginning anyway,
2355     * but also when receiving holdings of a crazyhouse game. In the latter
2356     * case we want to add those holdings to the already received position.
2357     */
2358
2359
2360    if (appData.debugMode) {
2361      fprintf(debugFP, "Switch board from %s to %s\n",
2362              VariantName(gameInfo.variant), VariantName(newVariant));
2363      setbuf(debugFP, NULL);
2364    }
2365    shuffleOpenings = 0;       /* [HGM] shuffle */
2366    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2367    switch(newVariant)
2368      {
2369      case VariantShogi:
2370        newWidth = 9;  newHeight = 9;
2371        gameInfo.holdingsSize = 7;
2372      case VariantBughouse:
2373      case VariantCrazyhouse:
2374        newHoldingsWidth = 2; break;
2375      case VariantGreat:
2376        newWidth = 10;
2377      case VariantSuper:
2378        newHoldingsWidth = 2;
2379        gameInfo.holdingsSize = 8;
2380        break;
2381      case VariantGothic:
2382      case VariantCapablanca:
2383      case VariantCapaRandom:
2384        newWidth = 10;
2385      default:
2386        newHoldingsWidth = gameInfo.holdingsSize = 0;
2387      };
2388
2389    if(newWidth  != gameInfo.boardWidth  ||
2390       newHeight != gameInfo.boardHeight ||
2391       newHoldingsWidth != gameInfo.holdingsWidth ) {
2392
2393      /* shift position to new playing area, if needed */
2394      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2395        for(i=0; i<BOARD_HEIGHT; i++)
2396          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2397            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2398              board[i][j];
2399        for(i=0; i<newHeight; i++) {
2400          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2401          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2402        }
2403      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2404        for(i=0; i<BOARD_HEIGHT; i++)
2405          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2406            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2407              board[i][j];
2408      }
2409      gameInfo.boardWidth  = newWidth;
2410      gameInfo.boardHeight = newHeight;
2411      gameInfo.holdingsWidth = newHoldingsWidth;
2412      gameInfo.variant = newVariant;
2413      InitDrawingSizes(-2, 0);
2414    } else gameInfo.variant = newVariant;
2415    CopyBoard(oldBoard, board);   // remember correctly formatted board
2416      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2417    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2418 }
2419
2420 static int loggedOn = FALSE;
2421
2422 /*-- Game start info cache: --*/
2423 int gs_gamenum;
2424 char gs_kind[MSG_SIZ];
2425 static char player1Name[128] = "";
2426 static char player2Name[128] = "";
2427 static char cont_seq[] = "\n\\   ";
2428 static int player1Rating = -1;
2429 static int player2Rating = -1;
2430 /*----------------------------*/
2431
2432 ColorClass curColor = ColorNormal;
2433 int suppressKibitz = 0;
2434
2435 // [HGM] seekgraph
2436 Boolean soughtPending = FALSE;
2437 Boolean seekGraphUp;
2438 #define MAX_SEEK_ADS 200
2439 #define SQUARE 0x80
2440 char *seekAdList[MAX_SEEK_ADS];
2441 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2442 float tcList[MAX_SEEK_ADS];
2443 char colorList[MAX_SEEK_ADS];
2444 int nrOfSeekAds = 0;
2445 int minRating = 1010, maxRating = 2800;
2446 int hMargin = 10, vMargin = 20, h, w;
2447 extern int squareSize, lineGap;
2448
2449 void
2450 PlotSeekAd (int i)
2451 {
2452         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2453         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2454         if(r < minRating+100 && r >=0 ) r = minRating+100;
2455         if(r > maxRating) r = maxRating;
2456         if(tc < 1.) tc = 1.;
2457         if(tc > 95.) tc = 95.;
2458         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2459         y = ((double)r - minRating)/(maxRating - minRating)
2460             * (h-vMargin-squareSize/8-1) + vMargin;
2461         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2462         if(strstr(seekAdList[i], " u ")) color = 1;
2463         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2464            !strstr(seekAdList[i], "bullet") &&
2465            !strstr(seekAdList[i], "blitz") &&
2466            !strstr(seekAdList[i], "standard") ) color = 2;
2467         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2468         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2469 }
2470
2471 void
2472 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2473 {
2474         char buf[MSG_SIZ], *ext = "";
2475         VariantClass v = StringToVariant(type);
2476         if(strstr(type, "wild")) {
2477             ext = type + 4; // append wild number
2478             if(v == VariantFischeRandom) type = "chess960"; else
2479             if(v == VariantLoadable) type = "setup"; else
2480             type = VariantName(v);
2481         }
2482         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2483         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2484             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2485             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2486             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2487             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2488             seekNrList[nrOfSeekAds] = nr;
2489             zList[nrOfSeekAds] = 0;
2490             seekAdList[nrOfSeekAds++] = StrSave(buf);
2491             if(plot) PlotSeekAd(nrOfSeekAds-1);
2492         }
2493 }
2494
2495 void
2496 EraseSeekDot (int i)
2497 {
2498     int x = xList[i], y = yList[i], d=squareSize/4, k;
2499     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2500     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2501     // now replot every dot that overlapped
2502     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2503         int xx = xList[k], yy = yList[k];
2504         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2505             DrawSeekDot(xx, yy, colorList[k]);
2506     }
2507 }
2508
2509 void
2510 RemoveSeekAd (int nr)
2511 {
2512         int i;
2513         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2514             EraseSeekDot(i);
2515             if(seekAdList[i]) free(seekAdList[i]);
2516             seekAdList[i] = seekAdList[--nrOfSeekAds];
2517             seekNrList[i] = seekNrList[nrOfSeekAds];
2518             ratingList[i] = ratingList[nrOfSeekAds];
2519             colorList[i]  = colorList[nrOfSeekAds];
2520             tcList[i] = tcList[nrOfSeekAds];
2521             xList[i]  = xList[nrOfSeekAds];
2522             yList[i]  = yList[nrOfSeekAds];
2523             zList[i]  = zList[nrOfSeekAds];
2524             seekAdList[nrOfSeekAds] = NULL;
2525             break;
2526         }
2527 }
2528
2529 Boolean
2530 MatchSoughtLine (char *line)
2531 {
2532     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2533     int nr, base, inc, u=0; char dummy;
2534
2535     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2536        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2537        (u=1) &&
2538        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2539         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2540         // match: compact and save the line
2541         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2542         return TRUE;
2543     }
2544     return FALSE;
2545 }
2546
2547 int
2548 DrawSeekGraph ()
2549 {
2550     int i;
2551     if(!seekGraphUp) return FALSE;
2552     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2553     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2554
2555     DrawSeekBackground(0, 0, w, h);
2556     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2557     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2558     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2559         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2560         yy = h-1-yy;
2561         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2562         if(i%500 == 0) {
2563             char buf[MSG_SIZ];
2564             snprintf(buf, MSG_SIZ, "%d", i);
2565             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2566         }
2567     }
2568     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2569     for(i=1; i<100; i+=(i<10?1:5)) {
2570         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2571         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2572         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2573             char buf[MSG_SIZ];
2574             snprintf(buf, MSG_SIZ, "%d", i);
2575             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2576         }
2577     }
2578     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2579     return TRUE;
2580 }
2581
2582 int
2583 SeekGraphClick (ClickType click, int x, int y, int moving)
2584 {
2585     static int lastDown = 0, displayed = 0, lastSecond;
2586     if(y < 0) return FALSE;
2587     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2588         if(click == Release || moving) return FALSE;
2589         nrOfSeekAds = 0;
2590         soughtPending = TRUE;
2591         SendToICS(ics_prefix);
2592         SendToICS("sought\n"); // should this be "sought all"?
2593     } else { // issue challenge based on clicked ad
2594         int dist = 10000; int i, closest = 0, second = 0;
2595         for(i=0; i<nrOfSeekAds; i++) {
2596             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2597             if(d < dist) { dist = d; closest = i; }
2598             second += (d - zList[i] < 120); // count in-range ads
2599             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2600         }
2601         if(dist < 120) {
2602             char buf[MSG_SIZ];
2603             second = (second > 1);
2604             if(displayed != closest || second != lastSecond) {
2605                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2606                 lastSecond = second; displayed = closest;
2607             }
2608             if(click == Press) {
2609                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2610                 lastDown = closest;
2611                 return TRUE;
2612             } // on press 'hit', only show info
2613             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2614             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2615             SendToICS(ics_prefix);
2616             SendToICS(buf);
2617             return TRUE; // let incoming board of started game pop down the graph
2618         } else if(click == Release) { // release 'miss' is ignored
2619             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2620             if(moving == 2) { // right up-click
2621                 nrOfSeekAds = 0; // refresh graph
2622                 soughtPending = TRUE;
2623                 SendToICS(ics_prefix);
2624                 SendToICS("sought\n"); // should this be "sought all"?
2625             }
2626             return TRUE;
2627         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2628         // press miss or release hit 'pop down' seek graph
2629         seekGraphUp = FALSE;
2630         DrawPosition(TRUE, NULL);
2631     }
2632     return TRUE;
2633 }
2634
2635 void
2636 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2637 {
2638 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2639 #define STARTED_NONE 0
2640 #define STARTED_MOVES 1
2641 #define STARTED_BOARD 2
2642 #define STARTED_OBSERVE 3
2643 #define STARTED_HOLDINGS 4
2644 #define STARTED_CHATTER 5
2645 #define STARTED_COMMENT 6
2646 #define STARTED_MOVES_NOHIDE 7
2647
2648     static int started = STARTED_NONE;
2649     static char parse[20000];
2650     static int parse_pos = 0;
2651     static char buf[BUF_SIZE + 1];
2652     static int firstTime = TRUE, intfSet = FALSE;
2653     static ColorClass prevColor = ColorNormal;
2654     static int savingComment = FALSE;
2655     static int cmatch = 0; // continuation sequence match
2656     char *bp;
2657     char str[MSG_SIZ];
2658     int i, oldi;
2659     int buf_len;
2660     int next_out;
2661     int tkind;
2662     int backup;    /* [DM] For zippy color lines */
2663     char *p;
2664     char talker[MSG_SIZ]; // [HGM] chat
2665     int channel;
2666
2667     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2668
2669     if (appData.debugMode) {
2670       if (!error) {
2671         fprintf(debugFP, "<ICS: ");
2672         show_bytes(debugFP, data, count);
2673         fprintf(debugFP, "\n");
2674       }
2675     }
2676
2677     if (appData.debugMode) { int f = forwardMostMove;
2678         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2679                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2680                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2681     }
2682     if (count > 0) {
2683         /* If last read ended with a partial line that we couldn't parse,
2684            prepend it to the new read and try again. */
2685         if (leftover_len > 0) {
2686             for (i=0; i<leftover_len; i++)
2687               buf[i] = buf[leftover_start + i];
2688         }
2689
2690     /* copy new characters into the buffer */
2691     bp = buf + leftover_len;
2692     buf_len=leftover_len;
2693     for (i=0; i<count; i++)
2694     {
2695         // ignore these
2696         if (data[i] == '\r')
2697             continue;
2698
2699         // join lines split by ICS?
2700         if (!appData.noJoin)
2701         {
2702             /*
2703                 Joining just consists of finding matches against the
2704                 continuation sequence, and discarding that sequence
2705                 if found instead of copying it.  So, until a match
2706                 fails, there's nothing to do since it might be the
2707                 complete sequence, and thus, something we don't want
2708                 copied.
2709             */
2710             if (data[i] == cont_seq[cmatch])
2711             {
2712                 cmatch++;
2713                 if (cmatch == strlen(cont_seq))
2714                 {
2715                     cmatch = 0; // complete match.  just reset the counter
2716
2717                     /*
2718                         it's possible for the ICS to not include the space
2719                         at the end of the last word, making our [correct]
2720                         join operation fuse two separate words.  the server
2721                         does this when the space occurs at the width setting.
2722                     */
2723                     if (!buf_len || buf[buf_len-1] != ' ')
2724                     {
2725                         *bp++ = ' ';
2726                         buf_len++;
2727                     }
2728                 }
2729                 continue;
2730             }
2731             else if (cmatch)
2732             {
2733                 /*
2734                     match failed, so we have to copy what matched before
2735                     falling through and copying this character.  In reality,
2736                     this will only ever be just the newline character, but
2737                     it doesn't hurt to be precise.
2738                 */
2739                 strncpy(bp, cont_seq, cmatch);
2740                 bp += cmatch;
2741                 buf_len += cmatch;
2742                 cmatch = 0;
2743             }
2744         }
2745
2746         // copy this char
2747         *bp++ = data[i];
2748         buf_len++;
2749     }
2750
2751         buf[buf_len] = NULLCHAR;
2752 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2753         next_out = 0;
2754         leftover_start = 0;
2755
2756         i = 0;
2757         while (i < buf_len) {
2758             /* Deal with part of the TELNET option negotiation
2759                protocol.  We refuse to do anything beyond the
2760                defaults, except that we allow the WILL ECHO option,
2761                which ICS uses to turn off password echoing when we are
2762                directly connected to it.  We reject this option
2763                if localLineEditing mode is on (always on in xboard)
2764                and we are talking to port 23, which might be a real
2765                telnet server that will try to keep WILL ECHO on permanently.
2766              */
2767             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2768                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2769                 unsigned char option;
2770                 oldi = i;
2771                 switch ((unsigned char) buf[++i]) {
2772                   case TN_WILL:
2773                     if (appData.debugMode)
2774                       fprintf(debugFP, "\n<WILL ");
2775                     switch (option = (unsigned char) buf[++i]) {
2776                       case TN_ECHO:
2777                         if (appData.debugMode)
2778                           fprintf(debugFP, "ECHO ");
2779                         /* Reply only if this is a change, according
2780                            to the protocol rules. */
2781                         if (remoteEchoOption) break;
2782                         if (appData.localLineEditing &&
2783                             atoi(appData.icsPort) == TN_PORT) {
2784                             TelnetRequest(TN_DONT, TN_ECHO);
2785                         } else {
2786                             EchoOff();
2787                             TelnetRequest(TN_DO, TN_ECHO);
2788                             remoteEchoOption = TRUE;
2789                         }
2790                         break;
2791                       default:
2792                         if (appData.debugMode)
2793                           fprintf(debugFP, "%d ", option);
2794                         /* Whatever this is, we don't want it. */
2795                         TelnetRequest(TN_DONT, option);
2796                         break;
2797                     }
2798                     break;
2799                   case TN_WONT:
2800                     if (appData.debugMode)
2801                       fprintf(debugFP, "\n<WONT ");
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                         EchoOn();
2810                         TelnetRequest(TN_DONT, TN_ECHO);
2811                         remoteEchoOption = FALSE;
2812                         break;
2813                       default:
2814                         if (appData.debugMode)
2815                           fprintf(debugFP, "%d ", (unsigned char) option);
2816                         /* Whatever this is, it must already be turned
2817                            off, because we never agree to turn on
2818                            anything non-default, so according to the
2819                            protocol rules, we don't reply. */
2820                         break;
2821                     }
2822                     break;
2823                   case TN_DO:
2824                     if (appData.debugMode)
2825                       fprintf(debugFP, "\n<DO ");
2826                     switch (option = (unsigned char) buf[++i]) {
2827                       default:
2828                         /* Whatever this is, we refuse to do it. */
2829                         if (appData.debugMode)
2830                           fprintf(debugFP, "%d ", option);
2831                         TelnetRequest(TN_WONT, option);
2832                         break;
2833                     }
2834                     break;
2835                   case TN_DONT:
2836                     if (appData.debugMode)
2837                       fprintf(debugFP, "\n<DONT ");
2838                     switch (option = (unsigned char) buf[++i]) {
2839                       default:
2840                         if (appData.debugMode)
2841                           fprintf(debugFP, "%d ", option);
2842                         /* Whatever this is, we are already not doing
2843                            it, because we never agree to do anything
2844                            non-default, so according to the protocol
2845                            rules, we don't reply. */
2846                         break;
2847                     }
2848                     break;
2849                   case TN_IAC:
2850                     if (appData.debugMode)
2851                       fprintf(debugFP, "\n<IAC ");
2852                     /* Doubled IAC; pass it through */
2853                     i--;
2854                     break;
2855                   default:
2856                     if (appData.debugMode)
2857                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2858                     /* Drop all other telnet commands on the floor */
2859                     break;
2860                 }
2861                 if (oldi > next_out)
2862                   SendToPlayer(&buf[next_out], oldi - next_out);
2863                 if (++i > next_out)
2864                   next_out = i;
2865                 continue;
2866             }
2867
2868             /* OK, this at least will *usually* work */
2869             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2870                 loggedOn = TRUE;
2871             }
2872
2873             if (loggedOn && !intfSet) {
2874                 if (ics_type == ICS_ICC) {
2875                   snprintf(str, MSG_SIZ,
2876                           "/set-quietly interface %s\n/set-quietly style 12\n",
2877                           programVersion);
2878                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2879                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2880                 } else if (ics_type == ICS_CHESSNET) {
2881                   snprintf(str, MSG_SIZ, "/style 12\n");
2882                 } else {
2883                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2884                   strcat(str, programVersion);
2885                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2886                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2887                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2888 #ifdef WIN32
2889                   strcat(str, "$iset nohighlight 1\n");
2890 #endif
2891                   strcat(str, "$iset lock 1\n$style 12\n");
2892                 }
2893                 SendToICS(str);
2894                 NotifyFrontendLogin();
2895                 intfSet = TRUE;
2896             }
2897
2898             if (started == STARTED_COMMENT) {
2899                 /* Accumulate characters in comment */
2900                 parse[parse_pos++] = buf[i];
2901                 if (buf[i] == '\n') {
2902                     parse[parse_pos] = NULLCHAR;
2903                     if(chattingPartner>=0) {
2904                         char mess[MSG_SIZ];
2905                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2906                         OutputChatMessage(chattingPartner, mess);
2907                         chattingPartner = -1;
2908                         next_out = i+1; // [HGM] suppress printing in ICS window
2909                     } else
2910                     if(!suppressKibitz) // [HGM] kibitz
2911                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2912                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2913                         int nrDigit = 0, nrAlph = 0, j;
2914                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2915                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2916                         parse[parse_pos] = NULLCHAR;
2917                         // try to be smart: if it does not look like search info, it should go to
2918                         // ICS interaction window after all, not to engine-output window.
2919                         for(j=0; j<parse_pos; j++) { // count letters and digits
2920                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2921                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2922                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2923                         }
2924                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2925                             int depth=0; float score;
2926                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2927                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2928                                 pvInfoList[forwardMostMove-1].depth = depth;
2929                                 pvInfoList[forwardMostMove-1].score = 100*score;
2930                             }
2931                             OutputKibitz(suppressKibitz, parse);
2932                         } else {
2933                             char tmp[MSG_SIZ];
2934                             if(gameMode == IcsObserving) // restore original ICS messages
2935                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2936                             else
2937                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2938                             SendToPlayer(tmp, strlen(tmp));
2939                         }
2940                         next_out = i+1; // [HGM] suppress printing in ICS window
2941                     }
2942                     started = STARTED_NONE;
2943                 } else {
2944                     /* Don't match patterns against characters in comment */
2945                     i++;
2946                     continue;
2947                 }
2948             }
2949             if (started == STARTED_CHATTER) {
2950                 if (buf[i] != '\n') {
2951                     /* Don't match patterns against characters in chatter */
2952                     i++;
2953                     continue;
2954                 }
2955                 started = STARTED_NONE;
2956                 if(suppressKibitz) next_out = i+1;
2957             }
2958
2959             /* Kludge to deal with rcmd protocol */
2960             if (firstTime && looking_at(buf, &i, "\001*")) {
2961                 DisplayFatalError(&buf[1], 0, 1);
2962                 continue;
2963             } else {
2964                 firstTime = FALSE;
2965             }
2966
2967             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2968                 ics_type = ICS_ICC;
2969                 ics_prefix = "/";
2970                 if (appData.debugMode)
2971                   fprintf(debugFP, "ics_type %d\n", ics_type);
2972                 continue;
2973             }
2974             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2975                 ics_type = ICS_FICS;
2976                 ics_prefix = "$";
2977                 if (appData.debugMode)
2978                   fprintf(debugFP, "ics_type %d\n", ics_type);
2979                 continue;
2980             }
2981             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2982                 ics_type = ICS_CHESSNET;
2983                 ics_prefix = "/";
2984                 if (appData.debugMode)
2985                   fprintf(debugFP, "ics_type %d\n", ics_type);
2986                 continue;
2987             }
2988
2989             if (!loggedOn &&
2990                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2991                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2992                  looking_at(buf, &i, "will be \"*\""))) {
2993               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2994               continue;
2995             }
2996
2997             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2998               char buf[MSG_SIZ];
2999               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3000               DisplayIcsInteractionTitle(buf);
3001               have_set_title = TRUE;
3002             }
3003
3004             /* skip finger notes */
3005             if (started == STARTED_NONE &&
3006                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3007                  (buf[i] == '1' && buf[i+1] == '0')) &&
3008                 buf[i+2] == ':' && buf[i+3] == ' ') {
3009               started = STARTED_CHATTER;
3010               i += 3;
3011               continue;
3012             }
3013
3014             oldi = i;
3015             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3016             if(appData.seekGraph) {
3017                 if(soughtPending && MatchSoughtLine(buf+i)) {
3018                     i = strstr(buf+i, "rated") - buf;
3019                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3020                     next_out = leftover_start = i;
3021                     started = STARTED_CHATTER;
3022                     suppressKibitz = TRUE;
3023                     continue;
3024                 }
3025                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3026                         && looking_at(buf, &i, "* ads displayed")) {
3027                     soughtPending = FALSE;
3028                     seekGraphUp = TRUE;
3029                     DrawSeekGraph();
3030                     continue;
3031                 }
3032                 if(appData.autoRefresh) {
3033                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3034                         int s = (ics_type == ICS_ICC); // ICC format differs
3035                         if(seekGraphUp)
3036                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3037                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3038                         looking_at(buf, &i, "*% "); // eat prompt
3039                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3040                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3041                         next_out = i; // suppress
3042                         continue;
3043                     }
3044                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3045                         char *p = star_match[0];
3046                         while(*p) {
3047                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3048                             while(*p && *p++ != ' '); // next
3049                         }
3050                         looking_at(buf, &i, "*% "); // eat prompt
3051                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3052                         next_out = i;
3053                         continue;
3054                     }
3055                 }
3056             }
3057
3058             /* skip formula vars */
3059             if (started == STARTED_NONE &&
3060                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3061               started = STARTED_CHATTER;
3062               i += 3;
3063               continue;
3064             }
3065
3066             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3067             if (appData.autoKibitz && started == STARTED_NONE &&
3068                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3069                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3070                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3071                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3072                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3073                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3074                         suppressKibitz = TRUE;
3075                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3076                         next_out = i;
3077                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3078                                 && (gameMode == IcsPlayingWhite)) ||
3079                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3080                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3081                             started = STARTED_CHATTER; // own kibitz we simply discard
3082                         else {
3083                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3084                             parse_pos = 0; parse[0] = NULLCHAR;
3085                             savingComment = TRUE;
3086                             suppressKibitz = gameMode != IcsObserving ? 2 :
3087                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3088                         }
3089                         continue;
3090                 } else
3091                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3092                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3093                          && atoi(star_match[0])) {
3094                     // suppress the acknowledgements of our own autoKibitz
3095                     char *p;
3096                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3097                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3098                     SendToPlayer(star_match[0], strlen(star_match[0]));
3099                     if(looking_at(buf, &i, "*% ")) // eat prompt
3100                         suppressKibitz = FALSE;
3101                     next_out = i;
3102                     continue;
3103                 }
3104             } // [HGM] kibitz: end of patch
3105
3106             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3107
3108             // [HGM] chat: intercept tells by users for which we have an open chat window
3109             channel = -1;
3110             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3111                                            looking_at(buf, &i, "* whispers:") ||
3112                                            looking_at(buf, &i, "* kibitzes:") ||
3113                                            looking_at(buf, &i, "* shouts:") ||
3114                                            looking_at(buf, &i, "* c-shouts:") ||
3115                                            looking_at(buf, &i, "--> * ") ||
3116                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3117                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3118                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3119                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3120                 int p;
3121                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3122                 chattingPartner = -1;
3123
3124                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3125                 for(p=0; p<MAX_CHAT; p++) {
3126                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3127                     talker[0] = '['; strcat(talker, "] ");
3128                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3129                     chattingPartner = p; break;
3130                     }
3131                 } else
3132                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3133                 for(p=0; p<MAX_CHAT; p++) {
3134                     if(!strcmp("kibitzes", chatPartner[p])) {
3135                         talker[0] = '['; strcat(talker, "] ");
3136                         chattingPartner = p; break;
3137                     }
3138                 } else
3139                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3140                 for(p=0; p<MAX_CHAT; p++) {
3141                     if(!strcmp("whispers", chatPartner[p])) {
3142                         talker[0] = '['; strcat(talker, "] ");
3143                         chattingPartner = p; break;
3144                     }
3145                 } else
3146                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3147                   if(buf[i-8] == '-' && buf[i-3] == 't')
3148                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3149                     if(!strcmp("c-shouts", chatPartner[p])) {
3150                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3151                         chattingPartner = p; break;
3152                     }
3153                   }
3154                   if(chattingPartner < 0)
3155                   for(p=0; p<MAX_CHAT; p++) {
3156                     if(!strcmp("shouts", chatPartner[p])) {
3157                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3158                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3159                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3160                         chattingPartner = p; break;
3161                     }
3162                   }
3163                 }
3164                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3165                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3166                     talker[0] = 0; Colorize(ColorTell, FALSE);
3167                     chattingPartner = p; break;
3168                 }
3169                 if(chattingPartner<0) i = oldi; else {
3170                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3171                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3172                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3173                     started = STARTED_COMMENT;
3174                     parse_pos = 0; parse[0] = NULLCHAR;
3175                     savingComment = 3 + chattingPartner; // counts as TRUE
3176                     suppressKibitz = TRUE;
3177                     continue;
3178                 }
3179             } // [HGM] chat: end of patch
3180
3181           backup = i;
3182             if (appData.zippyTalk || appData.zippyPlay) {
3183                 /* [DM] Backup address for color zippy lines */
3184 #if ZIPPY
3185                if (loggedOn == TRUE)
3186                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3187                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3188 #endif
3189             } // [DM] 'else { ' deleted
3190                 if (
3191                     /* Regular tells and says */
3192                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3193                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3194                     looking_at(buf, &i, "* says: ") ||
3195                     /* Don't color "message" or "messages" output */
3196                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3197                     looking_at(buf, &i, "*. * at *:*: ") ||
3198                     looking_at(buf, &i, "--* (*:*): ") ||
3199                     /* Message notifications (same color as tells) */
3200                     looking_at(buf, &i, "* has left a message ") ||
3201                     looking_at(buf, &i, "* just sent you a message:\n") ||
3202                     /* Whispers and kibitzes */
3203                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3204                     looking_at(buf, &i, "* kibitzes: ") ||
3205                     /* Channel tells */
3206                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3207
3208                   if (tkind == 1 && strchr(star_match[0], ':')) {
3209                       /* Avoid "tells you:" spoofs in channels */
3210                      tkind = 3;
3211                   }
3212                   if (star_match[0][0] == NULLCHAR ||
3213                       strchr(star_match[0], ' ') ||
3214                       (tkind == 3 && strchr(star_match[1], ' '))) {
3215                     /* Reject bogus matches */
3216                     i = oldi;
3217                   } else {
3218                     if (appData.colorize) {
3219                       if (oldi > next_out) {
3220                         SendToPlayer(&buf[next_out], oldi - next_out);
3221                         next_out = oldi;
3222                       }
3223                       switch (tkind) {
3224                       case 1:
3225                         Colorize(ColorTell, FALSE);
3226                         curColor = ColorTell;
3227                         break;
3228                       case 2:
3229                         Colorize(ColorKibitz, FALSE);
3230                         curColor = ColorKibitz;
3231                         break;
3232                       case 3:
3233                         p = strrchr(star_match[1], '(');
3234                         if (p == NULL) {
3235                           p = star_match[1];
3236                         } else {
3237                           p++;
3238                         }
3239                         if (atoi(p) == 1) {
3240                           Colorize(ColorChannel1, FALSE);
3241                           curColor = ColorChannel1;
3242                         } else {
3243                           Colorize(ColorChannel, FALSE);
3244                           curColor = ColorChannel;
3245                         }
3246                         break;
3247                       case 5:
3248                         curColor = ColorNormal;
3249                         break;
3250                       }
3251                     }
3252                     if (started == STARTED_NONE && appData.autoComment &&
3253                         (gameMode == IcsObserving ||
3254                          gameMode == IcsPlayingWhite ||
3255                          gameMode == IcsPlayingBlack)) {
3256                       parse_pos = i - oldi;
3257                       memcpy(parse, &buf[oldi], parse_pos);
3258                       parse[parse_pos] = NULLCHAR;
3259                       started = STARTED_COMMENT;
3260                       savingComment = TRUE;
3261                     } else {
3262                       started = STARTED_CHATTER;
3263                       savingComment = FALSE;
3264                     }
3265                     loggedOn = TRUE;
3266                     continue;
3267                   }
3268                 }
3269
3270                 if (looking_at(buf, &i, "* s-shouts: ") ||
3271                     looking_at(buf, &i, "* c-shouts: ")) {
3272                     if (appData.colorize) {
3273                         if (oldi > next_out) {
3274                             SendToPlayer(&buf[next_out], oldi - next_out);
3275                             next_out = oldi;
3276                         }
3277                         Colorize(ColorSShout, FALSE);
3278                         curColor = ColorSShout;
3279                     }
3280                     loggedOn = TRUE;
3281                     started = STARTED_CHATTER;
3282                     continue;
3283                 }
3284
3285                 if (looking_at(buf, &i, "--->")) {
3286                     loggedOn = TRUE;
3287                     continue;
3288                 }
3289
3290                 if (looking_at(buf, &i, "* shouts: ") ||
3291                     looking_at(buf, &i, "--> ")) {
3292                     if (appData.colorize) {
3293                         if (oldi > next_out) {
3294                             SendToPlayer(&buf[next_out], oldi - next_out);
3295                             next_out = oldi;
3296                         }
3297                         Colorize(ColorShout, FALSE);
3298                         curColor = ColorShout;
3299                     }
3300                     loggedOn = TRUE;
3301                     started = STARTED_CHATTER;
3302                     continue;
3303                 }
3304
3305                 if (looking_at( buf, &i, "Challenge:")) {
3306                     if (appData.colorize) {
3307                         if (oldi > next_out) {
3308                             SendToPlayer(&buf[next_out], oldi - next_out);
3309                             next_out = oldi;
3310                         }
3311                         Colorize(ColorChallenge, FALSE);
3312                         curColor = ColorChallenge;
3313                     }
3314                     loggedOn = TRUE;
3315                     continue;
3316                 }
3317
3318                 if (looking_at(buf, &i, "* offers you") ||
3319                     looking_at(buf, &i, "* offers to be") ||
3320                     looking_at(buf, &i, "* would like to") ||
3321                     looking_at(buf, &i, "* requests to") ||
3322                     looking_at(buf, &i, "Your opponent offers") ||
3323                     looking_at(buf, &i, "Your opponent requests")) {
3324
3325                     if (appData.colorize) {
3326                         if (oldi > next_out) {
3327                             SendToPlayer(&buf[next_out], oldi - next_out);
3328                             next_out = oldi;
3329                         }
3330                         Colorize(ColorRequest, FALSE);
3331                         curColor = ColorRequest;
3332                     }
3333                     continue;
3334                 }
3335
3336                 if (looking_at(buf, &i, "* (*) seeking")) {
3337                     if (appData.colorize) {
3338                         if (oldi > next_out) {
3339                             SendToPlayer(&buf[next_out], oldi - next_out);
3340                             next_out = oldi;
3341                         }
3342                         Colorize(ColorSeek, FALSE);
3343                         curColor = ColorSeek;
3344                     }
3345                     continue;
3346             }
3347
3348           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3349
3350             if (looking_at(buf, &i, "\\   ")) {
3351                 if (prevColor != ColorNormal) {
3352                     if (oldi > next_out) {
3353                         SendToPlayer(&buf[next_out], oldi - next_out);
3354                         next_out = oldi;
3355                     }
3356                     Colorize(prevColor, TRUE);
3357                     curColor = prevColor;
3358                 }
3359                 if (savingComment) {
3360                     parse_pos = i - oldi;
3361                     memcpy(parse, &buf[oldi], parse_pos);
3362                     parse[parse_pos] = NULLCHAR;
3363                     started = STARTED_COMMENT;
3364                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3365                         chattingPartner = savingComment - 3; // kludge to remember the box
3366                 } else {
3367                     started = STARTED_CHATTER;
3368                 }
3369                 continue;
3370             }
3371
3372             if (looking_at(buf, &i, "Black Strength :") ||
3373                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3374                 looking_at(buf, &i, "<10>") ||
3375                 looking_at(buf, &i, "#@#")) {
3376                 /* Wrong board style */
3377                 loggedOn = TRUE;
3378                 SendToICS(ics_prefix);
3379                 SendToICS("set style 12\n");
3380                 SendToICS(ics_prefix);
3381                 SendToICS("refresh\n");
3382                 continue;
3383             }
3384
3385             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3386                 ICSInitScript();
3387                 have_sent_ICS_logon = 1;
3388                 continue;
3389             }
3390
3391             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3392                 (looking_at(buf, &i, "\n<12> ") ||
3393                  looking_at(buf, &i, "<12> "))) {
3394                 loggedOn = TRUE;
3395                 if (oldi > next_out) {
3396                     SendToPlayer(&buf[next_out], oldi - next_out);
3397                 }
3398                 next_out = i;
3399                 started = STARTED_BOARD;
3400                 parse_pos = 0;
3401                 continue;
3402             }
3403
3404             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3405                 looking_at(buf, &i, "<b1> ")) {
3406                 if (oldi > next_out) {
3407                     SendToPlayer(&buf[next_out], oldi - next_out);
3408                 }
3409                 next_out = i;
3410                 started = STARTED_HOLDINGS;
3411                 parse_pos = 0;
3412                 continue;
3413             }
3414
3415             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3416                 loggedOn = TRUE;
3417                 /* Header for a move list -- first line */
3418
3419                 switch (ics_getting_history) {
3420                   case H_FALSE:
3421                     switch (gameMode) {
3422                       case IcsIdle:
3423                       case BeginningOfGame:
3424                         /* User typed "moves" or "oldmoves" while we
3425                            were idle.  Pretend we asked for these
3426                            moves and soak them up so user can step
3427                            through them and/or save them.
3428                            */
3429                         Reset(FALSE, TRUE);
3430                         gameMode = IcsObserving;
3431                         ModeHighlight();
3432                         ics_gamenum = -1;
3433                         ics_getting_history = H_GOT_UNREQ_HEADER;
3434                         break;
3435                       case EditGame: /*?*/
3436                       case EditPosition: /*?*/
3437                         /* Should above feature work in these modes too? */
3438                         /* For now it doesn't */
3439                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3440                         break;
3441                       default:
3442                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3443                         break;
3444                     }
3445                     break;
3446                   case H_REQUESTED:
3447                     /* Is this the right one? */
3448                     if (gameInfo.white && gameInfo.black &&
3449                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3450                         strcmp(gameInfo.black, star_match[2]) == 0) {
3451                         /* All is well */
3452                         ics_getting_history = H_GOT_REQ_HEADER;
3453                     }
3454                     break;
3455                   case H_GOT_REQ_HEADER:
3456                   case H_GOT_UNREQ_HEADER:
3457                   case H_GOT_UNWANTED_HEADER:
3458                   case H_GETTING_MOVES:
3459                     /* Should not happen */
3460                     DisplayError(_("Error gathering move list: two headers"), 0);
3461                     ics_getting_history = H_FALSE;
3462                     break;
3463                 }
3464
3465                 /* Save player ratings into gameInfo if needed */
3466                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3467                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3468                     (gameInfo.whiteRating == -1 ||
3469                      gameInfo.blackRating == -1)) {
3470
3471                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3472                     gameInfo.blackRating = string_to_rating(star_match[3]);
3473                     if (appData.debugMode)
3474                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3475                               gameInfo.whiteRating, gameInfo.blackRating);
3476                 }
3477                 continue;
3478             }
3479
3480             if (looking_at(buf, &i,
3481               "* * match, initial time: * minute*, increment: * second")) {
3482                 /* Header for a move list -- second line */
3483                 /* Initial board will follow if this is a wild game */
3484                 if (gameInfo.event != NULL) free(gameInfo.event);
3485                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3486                 gameInfo.event = StrSave(str);
3487                 /* [HGM] we switched variant. Translate boards if needed. */
3488                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3489                 continue;
3490             }
3491
3492             if (looking_at(buf, &i, "Move  ")) {
3493                 /* Beginning of a move list */
3494                 switch (ics_getting_history) {
3495                   case H_FALSE:
3496                     /* Normally should not happen */
3497                     /* Maybe user hit reset while we were parsing */
3498                     break;
3499                   case H_REQUESTED:
3500                     /* Happens if we are ignoring a move list that is not
3501                      * the one we just requested.  Common if the user
3502                      * tries to observe two games without turning off
3503                      * getMoveList */
3504                     break;
3505                   case H_GETTING_MOVES:
3506                     /* Should not happen */
3507                     DisplayError(_("Error gathering move list: nested"), 0);
3508                     ics_getting_history = H_FALSE;
3509                     break;
3510                   case H_GOT_REQ_HEADER:
3511                     ics_getting_history = H_GETTING_MOVES;
3512                     started = STARTED_MOVES;
3513                     parse_pos = 0;
3514                     if (oldi > next_out) {
3515                         SendToPlayer(&buf[next_out], oldi - next_out);
3516                     }
3517                     break;
3518                   case H_GOT_UNREQ_HEADER:
3519                     ics_getting_history = H_GETTING_MOVES;
3520                     started = STARTED_MOVES_NOHIDE;
3521                     parse_pos = 0;
3522                     break;
3523                   case H_GOT_UNWANTED_HEADER:
3524                     ics_getting_history = H_FALSE;
3525                     break;
3526                 }
3527                 continue;
3528             }
3529
3530             if (looking_at(buf, &i, "% ") ||
3531                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3532                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3533                 if(soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3534                     soughtPending = FALSE;
3535                     seekGraphUp = TRUE;
3536                     DrawSeekGraph();
3537                 }
3538                 if(suppressKibitz) next_out = i;
3539                 savingComment = FALSE;
3540                 suppressKibitz = 0;
3541                 switch (started) {
3542                   case STARTED_MOVES:
3543                   case STARTED_MOVES_NOHIDE:
3544                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3545                     parse[parse_pos + i - oldi] = NULLCHAR;
3546                     ParseGameHistory(parse);
3547 #if ZIPPY
3548                     if (appData.zippyPlay && first.initDone) {
3549                         FeedMovesToProgram(&first, forwardMostMove);
3550                         if (gameMode == IcsPlayingWhite) {
3551                             if (WhiteOnMove(forwardMostMove)) {
3552                                 if (first.sendTime) {
3553                                   if (first.useColors) {
3554                                     SendToProgram("black\n", &first);
3555                                   }
3556                                   SendTimeRemaining(&first, TRUE);
3557                                 }
3558                                 if (first.useColors) {
3559                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3560                                 }
3561                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3562                                 first.maybeThinking = TRUE;
3563                             } else {
3564                                 if (first.usePlayother) {
3565                                   if (first.sendTime) {
3566                                     SendTimeRemaining(&first, TRUE);
3567                                   }
3568                                   SendToProgram("playother\n", &first);
3569                                   firstMove = FALSE;
3570                                 } else {
3571                                   firstMove = TRUE;
3572                                 }
3573                             }
3574                         } else if (gameMode == IcsPlayingBlack) {
3575                             if (!WhiteOnMove(forwardMostMove)) {
3576                                 if (first.sendTime) {
3577                                   if (first.useColors) {
3578                                     SendToProgram("white\n", &first);
3579                                   }
3580                                   SendTimeRemaining(&first, FALSE);
3581                                 }
3582                                 if (first.useColors) {
3583                                   SendToProgram("black\n", &first);
3584                                 }
3585                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3586                                 first.maybeThinking = TRUE;
3587                             } else {
3588                                 if (first.usePlayother) {
3589                                   if (first.sendTime) {
3590                                     SendTimeRemaining(&first, FALSE);
3591                                   }
3592                                   SendToProgram("playother\n", &first);
3593                                   firstMove = FALSE;
3594                                 } else {
3595                                   firstMove = TRUE;
3596                                 }
3597                             }
3598                         }
3599                     }
3600 #endif
3601                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3602                         /* Moves came from oldmoves or moves command
3603                            while we weren't doing anything else.
3604                            */
3605                         currentMove = forwardMostMove;
3606                         ClearHighlights();/*!!could figure this out*/
3607                         flipView = appData.flipView;
3608                         DrawPosition(TRUE, boards[currentMove]);
3609                         DisplayBothClocks();
3610                         snprintf(str, MSG_SIZ, "%s %s %s",
3611                                 gameInfo.white, _("vs."),  gameInfo.black);
3612                         DisplayTitle(str);
3613                         gameMode = IcsIdle;
3614                     } else {
3615                         /* Moves were history of an active game */
3616                         if (gameInfo.resultDetails != NULL) {
3617                             free(gameInfo.resultDetails);
3618                             gameInfo.resultDetails = NULL;
3619                         }
3620                     }
3621                     HistorySet(parseList, backwardMostMove,
3622                                forwardMostMove, currentMove-1);
3623                     DisplayMove(currentMove - 1);
3624                     if (started == STARTED_MOVES) next_out = i;
3625                     started = STARTED_NONE;
3626                     ics_getting_history = H_FALSE;
3627                     break;
3628
3629                   case STARTED_OBSERVE:
3630                     started = STARTED_NONE;
3631                     SendToICS(ics_prefix);
3632                     SendToICS("refresh\n");
3633                     break;
3634
3635                   default:
3636                     break;
3637                 }
3638                 if(bookHit) { // [HGM] book: simulate book reply
3639                     static char bookMove[MSG_SIZ]; // a bit generous?
3640
3641                     programStats.nodes = programStats.depth = programStats.time =
3642                     programStats.score = programStats.got_only_move = 0;
3643                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3644
3645                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3646                     strcat(bookMove, bookHit);
3647                     HandleMachineMove(bookMove, &first);
3648                 }
3649                 continue;
3650             }
3651
3652             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3653                  started == STARTED_HOLDINGS ||
3654                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3655                 /* Accumulate characters in move list or board */
3656                 parse[parse_pos++] = buf[i];
3657             }
3658
3659             /* Start of game messages.  Mostly we detect start of game
3660                when the first board image arrives.  On some versions
3661                of the ICS, though, we need to do a "refresh" after starting
3662                to observe in order to get the current board right away. */
3663             if (looking_at(buf, &i, "Adding game * to observation list")) {
3664                 started = STARTED_OBSERVE;
3665                 continue;
3666             }
3667
3668             /* Handle auto-observe */
3669             if (appData.autoObserve &&
3670                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3671                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3672                 char *player;
3673                 /* Choose the player that was highlighted, if any. */
3674                 if (star_match[0][0] == '\033' ||
3675                     star_match[1][0] != '\033') {
3676                     player = star_match[0];
3677                 } else {
3678                     player = star_match[2];
3679                 }
3680                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3681                         ics_prefix, StripHighlightAndTitle(player));
3682                 SendToICS(str);
3683
3684                 /* Save ratings from notify string */
3685                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3686                 player1Rating = string_to_rating(star_match[1]);
3687                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3688                 player2Rating = string_to_rating(star_match[3]);
3689
3690                 if (appData.debugMode)
3691                   fprintf(debugFP,
3692                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3693                           player1Name, player1Rating,
3694                           player2Name, player2Rating);
3695
3696                 continue;
3697             }
3698
3699             /* Deal with automatic examine mode after a game,
3700                and with IcsObserving -> IcsExamining transition */
3701             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3702                 looking_at(buf, &i, "has made you an examiner of game *")) {
3703
3704                 int gamenum = atoi(star_match[0]);
3705                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3706                     gamenum == ics_gamenum) {
3707                     /* We were already playing or observing this game;
3708                        no need to refetch history */
3709                     gameMode = IcsExamining;
3710                     if (pausing) {
3711                         pauseExamForwardMostMove = forwardMostMove;
3712                     } else if (currentMove < forwardMostMove) {
3713                         ForwardInner(forwardMostMove);
3714                     }
3715                 } else {
3716                     /* I don't think this case really can happen */
3717                     SendToICS(ics_prefix);
3718                     SendToICS("refresh\n");
3719                 }
3720                 continue;
3721             }
3722
3723             /* Error messages */
3724 //          if (ics_user_moved) {
3725             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3726                 if (looking_at(buf, &i, "Illegal move") ||
3727                     looking_at(buf, &i, "Not a legal move") ||
3728                     looking_at(buf, &i, "Your king is in check") ||
3729                     looking_at(buf, &i, "It isn't your turn") ||
3730                     looking_at(buf, &i, "It is not your move")) {
3731                     /* Illegal move */
3732                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3733                         currentMove = forwardMostMove-1;
3734                         DisplayMove(currentMove - 1); /* before DMError */
3735                         DrawPosition(FALSE, boards[currentMove]);
3736                         SwitchClocks(forwardMostMove-1); // [HGM] race
3737                         DisplayBothClocks();
3738                     }
3739                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3740                     ics_user_moved = 0;
3741                     continue;
3742                 }
3743             }
3744
3745             if (looking_at(buf, &i, "still have time") ||
3746                 looking_at(buf, &i, "not out of time") ||
3747                 looking_at(buf, &i, "either player is out of time") ||
3748                 looking_at(buf, &i, "has timeseal; checking")) {
3749                 /* We must have called his flag a little too soon */
3750                 whiteFlag = blackFlag = FALSE;
3751                 continue;
3752             }
3753
3754             if (looking_at(buf, &i, "added * seconds to") ||
3755                 looking_at(buf, &i, "seconds were added to")) {
3756                 /* Update the clocks */
3757                 SendToICS(ics_prefix);
3758                 SendToICS("refresh\n");
3759                 continue;
3760             }
3761
3762             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3763                 ics_clock_paused = TRUE;
3764                 StopClocks();
3765                 continue;
3766             }
3767
3768             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3769                 ics_clock_paused = FALSE;
3770                 StartClocks();
3771                 continue;
3772             }
3773
3774             /* Grab player ratings from the Creating: message.
3775                Note we have to check for the special case when
3776                the ICS inserts things like [white] or [black]. */
3777             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3778                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3779                 /* star_matches:
3780                    0    player 1 name (not necessarily white)
3781                    1    player 1 rating
3782                    2    empty, white, or black (IGNORED)
3783                    3    player 2 name (not necessarily black)
3784                    4    player 2 rating
3785
3786                    The names/ratings are sorted out when the game
3787                    actually starts (below).
3788                 */
3789                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3790                 player1Rating = string_to_rating(star_match[1]);
3791                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3792                 player2Rating = string_to_rating(star_match[4]);
3793
3794                 if (appData.debugMode)
3795                   fprintf(debugFP,
3796                           "Ratings from 'Creating:' %s %d, %s %d\n",
3797                           player1Name, player1Rating,
3798                           player2Name, player2Rating);
3799
3800                 continue;
3801             }
3802
3803             /* Improved generic start/end-of-game messages */
3804             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3805                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3806                 /* If tkind == 0: */
3807                 /* star_match[0] is the game number */
3808                 /*           [1] is the white player's name */
3809                 /*           [2] is the black player's name */
3810                 /* For end-of-game: */
3811                 /*           [3] is the reason for the game end */
3812                 /*           [4] is a PGN end game-token, preceded by " " */
3813                 /* For start-of-game: */
3814                 /*           [3] begins with "Creating" or "Continuing" */
3815                 /*           [4] is " *" or empty (don't care). */
3816                 int gamenum = atoi(star_match[0]);
3817                 char *whitename, *blackname, *why, *endtoken;
3818                 ChessMove endtype = EndOfFile;
3819
3820                 if (tkind == 0) {
3821                   whitename = star_match[1];
3822                   blackname = star_match[2];
3823                   why = star_match[3];
3824                   endtoken = star_match[4];
3825                 } else {
3826                   whitename = star_match[1];
3827                   blackname = star_match[3];
3828                   why = star_match[5];
3829                   endtoken = star_match[6];
3830                 }
3831
3832                 /* Game start messages */
3833                 if (strncmp(why, "Creating ", 9) == 0 ||
3834                     strncmp(why, "Continuing ", 11) == 0) {
3835                     gs_gamenum = gamenum;
3836                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3837                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3838                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3839 #if ZIPPY
3840                     if (appData.zippyPlay) {
3841                         ZippyGameStart(whitename, blackname);
3842                     }
3843 #endif /*ZIPPY*/
3844                     partnerBoardValid = FALSE; // [HGM] bughouse
3845                     continue;
3846                 }
3847
3848                 /* Game end messages */
3849                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3850                     ics_gamenum != gamenum) {
3851                     continue;
3852                 }
3853                 while (endtoken[0] == ' ') endtoken++;
3854                 switch (endtoken[0]) {
3855                   case '*':
3856                   default:
3857                     endtype = GameUnfinished;
3858                     break;
3859                   case '0':
3860                     endtype = BlackWins;
3861                     break;
3862                   case '1':
3863                     if (endtoken[1] == '/')
3864                       endtype = GameIsDrawn;
3865                     else
3866                       endtype = WhiteWins;
3867                     break;
3868                 }
3869                 GameEnds(endtype, why, GE_ICS);
3870 #if ZIPPY
3871                 if (appData.zippyPlay && first.initDone) {
3872                     ZippyGameEnd(endtype, why);
3873                     if (first.pr == NoProc) {
3874                       /* Start the next process early so that we'll
3875                          be ready for the next challenge */
3876                       StartChessProgram(&first);
3877                     }
3878                     /* Send "new" early, in case this command takes
3879                        a long time to finish, so that we'll be ready
3880                        for the next challenge. */
3881                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3882                     Reset(TRUE, TRUE);
3883                 }
3884 #endif /*ZIPPY*/
3885                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3886                 continue;
3887             }
3888
3889             if (looking_at(buf, &i, "Removing game * from observation") ||
3890                 looking_at(buf, &i, "no longer observing game *") ||
3891                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3892                 if (gameMode == IcsObserving &&
3893                     atoi(star_match[0]) == ics_gamenum)
3894                   {
3895                       /* icsEngineAnalyze */
3896                       if (appData.icsEngineAnalyze) {
3897                             ExitAnalyzeMode();
3898                             ModeHighlight();
3899                       }
3900                       StopClocks();
3901                       gameMode = IcsIdle;
3902                       ics_gamenum = -1;
3903                       ics_user_moved = FALSE;
3904                   }
3905                 continue;
3906             }
3907
3908             if (looking_at(buf, &i, "no longer examining game *")) {
3909                 if (gameMode == IcsExamining &&
3910                     atoi(star_match[0]) == ics_gamenum)
3911                   {
3912                       gameMode = IcsIdle;
3913                       ics_gamenum = -1;
3914                       ics_user_moved = FALSE;
3915                   }
3916                 continue;
3917             }
3918
3919             /* Advance leftover_start past any newlines we find,
3920                so only partial lines can get reparsed */
3921             if (looking_at(buf, &i, "\n")) {
3922                 prevColor = curColor;
3923                 if (curColor != ColorNormal) {
3924                     if (oldi > next_out) {
3925                         SendToPlayer(&buf[next_out], oldi - next_out);
3926                         next_out = oldi;
3927                     }
3928                     Colorize(ColorNormal, FALSE);
3929                     curColor = ColorNormal;
3930                 }
3931                 if (started == STARTED_BOARD) {
3932                     started = STARTED_NONE;
3933                     parse[parse_pos] = NULLCHAR;
3934                     ParseBoard12(parse);
3935                     ics_user_moved = 0;
3936
3937                     /* Send premove here */
3938                     if (appData.premove) {
3939                       char str[MSG_SIZ];
3940                       if (currentMove == 0 &&
3941                           gameMode == IcsPlayingWhite &&
3942                           appData.premoveWhite) {
3943                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3944                         if (appData.debugMode)
3945                           fprintf(debugFP, "Sending premove:\n");
3946                         SendToICS(str);
3947                       } else if (currentMove == 1 &&
3948                                  gameMode == IcsPlayingBlack &&
3949                                  appData.premoveBlack) {
3950                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3951                         if (appData.debugMode)
3952                           fprintf(debugFP, "Sending premove:\n");
3953                         SendToICS(str);
3954                       } else if (gotPremove) {
3955                         gotPremove = 0;
3956                         ClearPremoveHighlights();
3957                         if (appData.debugMode)
3958                           fprintf(debugFP, "Sending premove:\n");
3959                           UserMoveEvent(premoveFromX, premoveFromY,
3960                                         premoveToX, premoveToY,
3961                                         premovePromoChar);
3962                       }
3963                     }
3964
3965                     /* Usually suppress following prompt */
3966                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3967                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3968                         if (looking_at(buf, &i, "*% ")) {
3969                             savingComment = FALSE;
3970                             suppressKibitz = 0;
3971                         }
3972                     }
3973                     next_out = i;
3974                 } else if (started == STARTED_HOLDINGS) {
3975                     int gamenum;
3976                     char new_piece[MSG_SIZ];
3977                     started = STARTED_NONE;
3978                     parse[parse_pos] = NULLCHAR;
3979                     if (appData.debugMode)
3980                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3981                                                         parse, currentMove);
3982                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3983                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3984                         if (gameInfo.variant == VariantNormal) {
3985                           /* [HGM] We seem to switch variant during a game!
3986                            * Presumably no holdings were displayed, so we have
3987                            * to move the position two files to the right to
3988                            * create room for them!
3989                            */
3990                           VariantClass newVariant;
3991                           switch(gameInfo.boardWidth) { // base guess on board width
3992                                 case 9:  newVariant = VariantShogi; break;
3993                                 case 10: newVariant = VariantGreat; break;
3994                                 default: newVariant = VariantCrazyhouse; break;
3995                           }
3996                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3997                           /* Get a move list just to see the header, which
3998                              will tell us whether this is really bug or zh */
3999                           if (ics_getting_history == H_FALSE) {
4000                             ics_getting_history = H_REQUESTED;
4001                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4002                             SendToICS(str);
4003                           }
4004                         }
4005                         new_piece[0] = NULLCHAR;
4006                         sscanf(parse, "game %d white [%s black [%s <- %s",
4007                                &gamenum, white_holding, black_holding,
4008                                new_piece);
4009                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4010                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4011                         /* [HGM] copy holdings to board holdings area */
4012                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4013                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4014                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4015 #if ZIPPY
4016                         if (appData.zippyPlay && first.initDone) {
4017                             ZippyHoldings(white_holding, black_holding,
4018                                           new_piece);
4019                         }
4020 #endif /*ZIPPY*/
4021                         if (tinyLayout || smallLayout) {
4022                             char wh[16], bh[16];
4023                             PackHolding(wh, white_holding);
4024                             PackHolding(bh, black_holding);
4025                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4026                                     gameInfo.white, gameInfo.black);
4027                         } else {
4028                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4029                                     gameInfo.white, white_holding, _("vs."),
4030                                     gameInfo.black, black_holding);
4031                         }
4032                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4033                         DrawPosition(FALSE, boards[currentMove]);
4034                         DisplayTitle(str);
4035                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4036                         sscanf(parse, "game %d white [%s black [%s <- %s",
4037                                &gamenum, white_holding, black_holding,
4038                                new_piece);
4039                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4040                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4041                         /* [HGM] copy holdings to partner-board holdings area */
4042                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4043                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4044                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4045                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4046                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4047                       }
4048                     }
4049                     /* Suppress following prompt */
4050                     if (looking_at(buf, &i, "*% ")) {
4051                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4052                         savingComment = FALSE;
4053                         suppressKibitz = 0;
4054                     }
4055                     next_out = i;
4056                 }
4057                 continue;
4058             }
4059
4060             i++;                /* skip unparsed character and loop back */
4061         }
4062
4063         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4064 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4065 //          SendToPlayer(&buf[next_out], i - next_out);
4066             started != STARTED_HOLDINGS && leftover_start > next_out) {
4067             SendToPlayer(&buf[next_out], leftover_start - next_out);
4068             next_out = i;
4069         }
4070
4071         leftover_len = buf_len - leftover_start;
4072         /* if buffer ends with something we couldn't parse,
4073            reparse it after appending the next read */
4074
4075     } else if (count == 0) {
4076         RemoveInputSource(isr);
4077         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4078     } else {
4079         DisplayFatalError(_("Error reading from ICS"), error, 1);
4080     }
4081 }
4082
4083
4084 /* Board style 12 looks like this:
4085
4086    <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
4087
4088  * The "<12> " is stripped before it gets to this routine.  The two
4089  * trailing 0's (flip state and clock ticking) are later addition, and
4090  * some chess servers may not have them, or may have only the first.
4091  * Additional trailing fields may be added in the future.
4092  */
4093
4094 #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"
4095
4096 #define RELATION_OBSERVING_PLAYED    0
4097 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4098 #define RELATION_PLAYING_MYMOVE      1
4099 #define RELATION_PLAYING_NOTMYMOVE  -1
4100 #define RELATION_EXAMINING           2
4101 #define RELATION_ISOLATED_BOARD     -3
4102 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4103
4104 void
4105 ParseBoard12 (char *string)
4106 {
4107     GameMode newGameMode;
4108     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4109     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4110     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4111     char to_play, board_chars[200];
4112     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4113     char black[32], white[32];
4114     Board board;
4115     int prevMove = currentMove;
4116     int ticking = 2;
4117     ChessMove moveType;
4118     int fromX, fromY, toX, toY;
4119     char promoChar;
4120     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4121     char *bookHit = NULL; // [HGM] book
4122     Boolean weird = FALSE, reqFlag = FALSE;
4123
4124     fromX = fromY = toX = toY = -1;
4125
4126     newGame = FALSE;
4127
4128     if (appData.debugMode)
4129       fprintf(debugFP, _("Parsing board: %s\n"), string);
4130
4131     move_str[0] = NULLCHAR;
4132     elapsed_time[0] = NULLCHAR;
4133     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4134         int  i = 0, j;
4135         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4136             if(string[i] == ' ') { ranks++; files = 0; }
4137             else files++;
4138             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4139             i++;
4140         }
4141         for(j = 0; j <i; j++) board_chars[j] = string[j];
4142         board_chars[i] = '\0';
4143         string += i + 1;
4144     }
4145     n = sscanf(string, PATTERN, &to_play, &double_push,
4146                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4147                &gamenum, white, black, &relation, &basetime, &increment,
4148                &white_stren, &black_stren, &white_time, &black_time,
4149                &moveNum, str, elapsed_time, move_str, &ics_flip,
4150                &ticking);
4151
4152     if (n < 21) {
4153         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4154         DisplayError(str, 0);
4155         return;
4156     }
4157
4158     /* Convert the move number to internal form */
4159     moveNum = (moveNum - 1) * 2;
4160     if (to_play == 'B') moveNum++;
4161     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4162       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4163                         0, 1);
4164       return;
4165     }
4166
4167     switch (relation) {
4168       case RELATION_OBSERVING_PLAYED:
4169       case RELATION_OBSERVING_STATIC:
4170         if (gamenum == -1) {
4171             /* Old ICC buglet */
4172             relation = RELATION_OBSERVING_STATIC;
4173         }
4174         newGameMode = IcsObserving;
4175         break;
4176       case RELATION_PLAYING_MYMOVE:
4177       case RELATION_PLAYING_NOTMYMOVE:
4178         newGameMode =
4179           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4180             IcsPlayingWhite : IcsPlayingBlack;
4181         break;
4182       case RELATION_EXAMINING:
4183         newGameMode = IcsExamining;
4184         break;
4185       case RELATION_ISOLATED_BOARD:
4186       default:
4187         /* Just display this board.  If user was doing something else,
4188            we will forget about it until the next board comes. */
4189         newGameMode = IcsIdle;
4190         break;
4191       case RELATION_STARTING_POSITION:
4192         newGameMode = gameMode;
4193         break;
4194     }
4195
4196     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4197          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4198       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4199       char *toSqr;
4200       for (k = 0; k < ranks; k++) {
4201         for (j = 0; j < files; j++)
4202           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4203         if(gameInfo.holdingsWidth > 1) {
4204              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4205              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4206         }
4207       }
4208       CopyBoard(partnerBoard, board);
4209       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4210         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4211         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4212       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4213       if(toSqr = strchr(str, '-')) {
4214         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4215         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4216       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4217       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4218       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4219       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4220       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4221       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4222                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4223       DisplayMessage(partnerStatus, "");
4224         partnerBoardValid = TRUE;
4225       return;
4226     }
4227
4228     /* Modify behavior for initial board display on move listing
4229        of wild games.
4230        */
4231     switch (ics_getting_history) {
4232       case H_FALSE:
4233       case H_REQUESTED:
4234         break;
4235       case H_GOT_REQ_HEADER:
4236       case H_GOT_UNREQ_HEADER:
4237         /* This is the initial position of the current game */
4238         gamenum = ics_gamenum;
4239         moveNum = 0;            /* old ICS bug workaround */
4240         if (to_play == 'B') {
4241           startedFromSetupPosition = TRUE;
4242           blackPlaysFirst = TRUE;
4243           moveNum = 1;
4244           if (forwardMostMove == 0) forwardMostMove = 1;
4245           if (backwardMostMove == 0) backwardMostMove = 1;
4246           if (currentMove == 0) currentMove = 1;
4247         }
4248         newGameMode = gameMode;
4249         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4250         break;
4251       case H_GOT_UNWANTED_HEADER:
4252         /* This is an initial board that we don't want */
4253         return;
4254       case H_GETTING_MOVES:
4255         /* Should not happen */
4256         DisplayError(_("Error gathering move list: extra board"), 0);
4257         ics_getting_history = H_FALSE;
4258         return;
4259     }
4260
4261    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4262                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4263      /* [HGM] We seem to have switched variant unexpectedly
4264       * Try to guess new variant from board size
4265       */
4266           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4267           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4268           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4269           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4270           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4271           if(!weird) newVariant = VariantNormal;
4272           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4273           /* Get a move list just to see the header, which
4274              will tell us whether this is really bug or zh */
4275           if (ics_getting_history == H_FALSE) {
4276             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4277             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4278             SendToICS(str);
4279           }
4280     }
4281
4282     /* Take action if this is the first board of a new game, or of a
4283        different game than is currently being displayed.  */
4284     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4285         relation == RELATION_ISOLATED_BOARD) {
4286
4287         /* Forget the old game and get the history (if any) of the new one */
4288         if (gameMode != BeginningOfGame) {
4289           Reset(TRUE, TRUE);
4290         }
4291         newGame = TRUE;
4292         if (appData.autoRaiseBoard) BoardToTop();
4293         prevMove = -3;
4294         if (gamenum == -1) {
4295             newGameMode = IcsIdle;
4296         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4297                    appData.getMoveList && !reqFlag) {
4298             /* Need to get game history */
4299             ics_getting_history = H_REQUESTED;
4300             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4301             SendToICS(str);
4302         }
4303
4304         /* Initially flip the board to have black on the bottom if playing
4305            black or if the ICS flip flag is set, but let the user change
4306            it with the Flip View button. */
4307         flipView = appData.autoFlipView ?
4308           (newGameMode == IcsPlayingBlack) || ics_flip :
4309           appData.flipView;
4310
4311         /* Done with values from previous mode; copy in new ones */
4312         gameMode = newGameMode;
4313         ModeHighlight();
4314         ics_gamenum = gamenum;
4315         if (gamenum == gs_gamenum) {
4316             int klen = strlen(gs_kind);
4317             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4318             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4319             gameInfo.event = StrSave(str);
4320         } else {
4321             gameInfo.event = StrSave("ICS game");
4322         }
4323         gameInfo.site = StrSave(appData.icsHost);
4324         gameInfo.date = PGNDate();
4325         gameInfo.round = StrSave("-");
4326         gameInfo.white = StrSave(white);
4327         gameInfo.black = StrSave(black);
4328         timeControl = basetime * 60 * 1000;
4329         timeControl_2 = 0;
4330         timeIncrement = increment * 1000;
4331         movesPerSession = 0;
4332         gameInfo.timeControl = TimeControlTagValue();
4333         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4334   if (appData.debugMode) {
4335     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4336     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4337     setbuf(debugFP, NULL);
4338   }
4339
4340         gameInfo.outOfBook = NULL;
4341
4342         /* Do we have the ratings? */
4343         if (strcmp(player1Name, white) == 0 &&
4344             strcmp(player2Name, black) == 0) {
4345             if (appData.debugMode)
4346               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4347                       player1Rating, player2Rating);
4348             gameInfo.whiteRating = player1Rating;
4349             gameInfo.blackRating = player2Rating;
4350         } else if (strcmp(player2Name, white) == 0 &&
4351                    strcmp(player1Name, black) == 0) {
4352             if (appData.debugMode)
4353               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4354                       player2Rating, player1Rating);
4355             gameInfo.whiteRating = player2Rating;
4356             gameInfo.blackRating = player1Rating;
4357         }
4358         player1Name[0] = player2Name[0] = NULLCHAR;
4359
4360         /* Silence shouts if requested */
4361         if (appData.quietPlay &&
4362             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4363             SendToICS(ics_prefix);
4364             SendToICS("set shout 0\n");
4365         }
4366     }
4367
4368     /* Deal with midgame name changes */
4369     if (!newGame) {
4370         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4371             if (gameInfo.white) free(gameInfo.white);
4372             gameInfo.white = StrSave(white);
4373         }
4374         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4375             if (gameInfo.black) free(gameInfo.black);
4376             gameInfo.black = StrSave(black);
4377         }
4378     }
4379
4380     /* Throw away game result if anything actually changes in examine mode */
4381     if (gameMode == IcsExamining && !newGame) {
4382         gameInfo.result = GameUnfinished;
4383         if (gameInfo.resultDetails != NULL) {
4384             free(gameInfo.resultDetails);
4385             gameInfo.resultDetails = NULL;
4386         }
4387     }
4388
4389     /* In pausing && IcsExamining mode, we ignore boards coming
4390        in if they are in a different variation than we are. */
4391     if (pauseExamInvalid) return;
4392     if (pausing && gameMode == IcsExamining) {
4393         if (moveNum <= pauseExamForwardMostMove) {
4394             pauseExamInvalid = TRUE;
4395             forwardMostMove = pauseExamForwardMostMove;
4396             return;
4397         }
4398     }
4399
4400   if (appData.debugMode) {
4401     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4402   }
4403     /* Parse the board */
4404     for (k = 0; k < ranks; k++) {
4405       for (j = 0; j < files; j++)
4406         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4407       if(gameInfo.holdingsWidth > 1) {
4408            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4409            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4410       }
4411     }
4412     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4413       board[5][BOARD_RGHT+1] = WhiteAngel;
4414       board[6][BOARD_RGHT+1] = WhiteMarshall;
4415       board[1][0] = BlackMarshall;
4416       board[2][0] = BlackAngel;
4417       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4418     }
4419     CopyBoard(boards[moveNum], board);
4420     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4421     if (moveNum == 0) {
4422         startedFromSetupPosition =
4423           !CompareBoards(board, initialPosition);
4424         if(startedFromSetupPosition)
4425             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4426     }
4427
4428     /* [HGM] Set castling rights. Take the outermost Rooks,
4429        to make it also work for FRC opening positions. Note that board12
4430        is really defective for later FRC positions, as it has no way to
4431        indicate which Rook can castle if they are on the same side of King.
4432        For the initial position we grant rights to the outermost Rooks,
4433        and remember thos rights, and we then copy them on positions
4434        later in an FRC game. This means WB might not recognize castlings with
4435        Rooks that have moved back to their original position as illegal,
4436        but in ICS mode that is not its job anyway.
4437     */
4438     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4439     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4440
4441         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4442             if(board[0][i] == WhiteRook) j = i;
4443         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4444         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4445             if(board[0][i] == WhiteRook) j = i;
4446         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4447         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4448             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4449         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4450         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4451             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4452         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4453
4454         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4455         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4456         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4457             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4458         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4459             if(board[BOARD_HEIGHT-1][k] == bKing)
4460                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4461         if(gameInfo.variant == VariantTwoKings) {
4462             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4463             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4464             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4465         }
4466     } else { int r;
4467         r = boards[moveNum][CASTLING][0] = initialRights[0];
4468         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4469         r = boards[moveNum][CASTLING][1] = initialRights[1];
4470         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4471         r = boards[moveNum][CASTLING][3] = initialRights[3];
4472         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4473         r = boards[moveNum][CASTLING][4] = initialRights[4];
4474         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4475         /* wildcastle kludge: always assume King has rights */
4476         r = boards[moveNum][CASTLING][2] = initialRights[2];
4477         r = boards[moveNum][CASTLING][5] = initialRights[5];
4478     }
4479     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4480     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4481
4482
4483     if (ics_getting_history == H_GOT_REQ_HEADER ||
4484         ics_getting_history == H_GOT_UNREQ_HEADER) {
4485         /* This was an initial position from a move list, not
4486            the current position */
4487         return;
4488     }
4489
4490     /* Update currentMove and known move number limits */
4491     newMove = newGame || moveNum > forwardMostMove;
4492
4493     if (newGame) {
4494         forwardMostMove = backwardMostMove = currentMove = moveNum;
4495         if (gameMode == IcsExamining && moveNum == 0) {
4496           /* Workaround for ICS limitation: we are not told the wild
4497              type when starting to examine a game.  But if we ask for
4498              the move list, the move list header will tell us */
4499             ics_getting_history = H_REQUESTED;
4500             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4501             SendToICS(str);
4502         }
4503     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4504                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4505 #if ZIPPY
4506         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4507         /* [HGM] applied this also to an engine that is silently watching        */
4508         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4509             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4510             gameInfo.variant == currentlyInitializedVariant) {
4511           takeback = forwardMostMove - moveNum;
4512           for (i = 0; i < takeback; i++) {
4513             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4514             SendToProgram("undo\n", &first);
4515           }
4516         }
4517 #endif
4518
4519         forwardMostMove = moveNum;
4520         if (!pausing || currentMove > forwardMostMove)
4521           currentMove = forwardMostMove;
4522     } else {
4523         /* New part of history that is not contiguous with old part */
4524         if (pausing && gameMode == IcsExamining) {
4525             pauseExamInvalid = TRUE;
4526             forwardMostMove = pauseExamForwardMostMove;
4527             return;
4528         }
4529         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4530 #if ZIPPY
4531             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4532                 // [HGM] when we will receive the move list we now request, it will be
4533                 // fed to the engine from the first move on. So if the engine is not
4534                 // in the initial position now, bring it there.
4535                 InitChessProgram(&first, 0);
4536             }
4537 #endif
4538             ics_getting_history = H_REQUESTED;
4539             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4540             SendToICS(str);
4541         }
4542         forwardMostMove = backwardMostMove = currentMove = moveNum;
4543     }
4544
4545     /* Update the clocks */
4546     if (strchr(elapsed_time, '.')) {
4547       /* Time is in ms */
4548       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4549       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4550     } else {
4551       /* Time is in seconds */
4552       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4553       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4554     }
4555
4556
4557 #if ZIPPY
4558     if (appData.zippyPlay && newGame &&
4559         gameMode != IcsObserving && gameMode != IcsIdle &&
4560         gameMode != IcsExamining)
4561       ZippyFirstBoard(moveNum, basetime, increment);
4562 #endif
4563
4564     /* Put the move on the move list, first converting
4565        to canonical algebraic form. */
4566     if (moveNum > 0) {
4567   if (appData.debugMode) {
4568     if (appData.debugMode) { int f = forwardMostMove;
4569         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4570                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4571                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4572     }
4573     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4574     fprintf(debugFP, "moveNum = %d\n", moveNum);
4575     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4576     setbuf(debugFP, NULL);
4577   }
4578         if (moveNum <= backwardMostMove) {
4579             /* We don't know what the board looked like before
4580                this move.  Punt. */
4581           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4582             strcat(parseList[moveNum - 1], " ");
4583             strcat(parseList[moveNum - 1], elapsed_time);
4584             moveList[moveNum - 1][0] = NULLCHAR;
4585         } else if (strcmp(move_str, "none") == 0) {
4586             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4587             /* Again, we don't know what the board looked like;
4588                this is really the start of the game. */
4589             parseList[moveNum - 1][0] = NULLCHAR;
4590             moveList[moveNum - 1][0] = NULLCHAR;
4591             backwardMostMove = moveNum;
4592             startedFromSetupPosition = TRUE;
4593             fromX = fromY = toX = toY = -1;
4594         } else {
4595           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4596           //                 So we parse the long-algebraic move string in stead of the SAN move
4597           int valid; char buf[MSG_SIZ], *prom;
4598
4599           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4600                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4601           // str looks something like "Q/a1-a2"; kill the slash
4602           if(str[1] == '/')
4603             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4604           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4605           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4606                 strcat(buf, prom); // long move lacks promo specification!
4607           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4608                 if(appData.debugMode)
4609                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4610                 safeStrCpy(move_str, buf, MSG_SIZ);
4611           }
4612           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4613                                 &fromX, &fromY, &toX, &toY, &promoChar)
4614                || ParseOneMove(buf, moveNum - 1, &moveType,
4615                                 &fromX, &fromY, &toX, &toY, &promoChar);
4616           // end of long SAN patch
4617           if (valid) {
4618             (void) CoordsToAlgebraic(boards[moveNum - 1],
4619                                      PosFlags(moveNum - 1),
4620                                      fromY, fromX, toY, toX, promoChar,
4621                                      parseList[moveNum-1]);
4622             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4623               case MT_NONE:
4624               case MT_STALEMATE:
4625               default:
4626                 break;
4627               case MT_CHECK:
4628                 if(gameInfo.variant != VariantShogi)
4629                     strcat(parseList[moveNum - 1], "+");
4630                 break;
4631               case MT_CHECKMATE:
4632               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4633                 strcat(parseList[moveNum - 1], "#");
4634                 break;
4635             }
4636             strcat(parseList[moveNum - 1], " ");
4637             strcat(parseList[moveNum - 1], elapsed_time);
4638             /* currentMoveString is set as a side-effect of ParseOneMove */
4639             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4640             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4641             strcat(moveList[moveNum - 1], "\n");
4642
4643             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4644                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4645               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4646                 ChessSquare old, new = boards[moveNum][k][j];
4647                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4648                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4649                   if(old == new) continue;
4650                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4651                   else if(new == WhiteWazir || new == BlackWazir) {
4652                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4653                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4654                       else boards[moveNum][k][j] = old; // preserve type of Gold
4655                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4656                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4657               }
4658           } else {
4659             /* Move from ICS was illegal!?  Punt. */
4660             if (appData.debugMode) {
4661               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4662               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4663             }
4664             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4665             strcat(parseList[moveNum - 1], " ");
4666             strcat(parseList[moveNum - 1], elapsed_time);
4667             moveList[moveNum - 1][0] = NULLCHAR;
4668             fromX = fromY = toX = toY = -1;
4669           }
4670         }
4671   if (appData.debugMode) {
4672     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4673     setbuf(debugFP, NULL);
4674   }
4675
4676 #if ZIPPY
4677         /* Send move to chess program (BEFORE animating it). */
4678         if (appData.zippyPlay && !newGame && newMove &&
4679            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4680
4681             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4682                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4683                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4684                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4685                             move_str);
4686                     DisplayError(str, 0);
4687                 } else {
4688                     if (first.sendTime) {
4689                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4690                     }
4691                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4692                     if (firstMove && !bookHit) {
4693                         firstMove = FALSE;
4694                         if (first.useColors) {
4695                           SendToProgram(gameMode == IcsPlayingWhite ?
4696                                         "white\ngo\n" :
4697                                         "black\ngo\n", &first);
4698                         } else {
4699                           SendToProgram("go\n", &first);
4700                         }
4701                         first.maybeThinking = TRUE;
4702                     }
4703                 }
4704             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4705               if (moveList[moveNum - 1][0] == NULLCHAR) {
4706                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4707                 DisplayError(str, 0);
4708               } else {
4709                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4710                 SendMoveToProgram(moveNum - 1, &first);
4711               }
4712             }
4713         }
4714 #endif
4715     }
4716
4717     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4718         /* If move comes from a remote source, animate it.  If it
4719            isn't remote, it will have already been animated. */
4720         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4721             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4722         }
4723         if (!pausing && appData.highlightLastMove) {
4724             SetHighlights(fromX, fromY, toX, toY);
4725         }
4726     }
4727
4728     /* Start the clocks */
4729     whiteFlag = blackFlag = FALSE;
4730     appData.clockMode = !(basetime == 0 && increment == 0);
4731     if (ticking == 0) {
4732       ics_clock_paused = TRUE;
4733       StopClocks();
4734     } else if (ticking == 1) {
4735       ics_clock_paused = FALSE;
4736     }
4737     if (gameMode == IcsIdle ||
4738         relation == RELATION_OBSERVING_STATIC ||
4739         relation == RELATION_EXAMINING ||
4740         ics_clock_paused)
4741       DisplayBothClocks();
4742     else
4743       StartClocks();
4744
4745     /* Display opponents and material strengths */
4746     if (gameInfo.variant != VariantBughouse &&
4747         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4748         if (tinyLayout || smallLayout) {
4749             if(gameInfo.variant == VariantNormal)
4750               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4751                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4752                     basetime, increment);
4753             else
4754               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4755                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4756                     basetime, increment, (int) gameInfo.variant);
4757         } else {
4758             if(gameInfo.variant == VariantNormal)
4759               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4760                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4761                     basetime, increment);
4762             else
4763               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4764                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4765                     basetime, increment, VariantName(gameInfo.variant));
4766         }
4767         DisplayTitle(str);
4768   if (appData.debugMode) {
4769     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4770   }
4771     }
4772
4773
4774     /* Display the board */
4775     if (!pausing && !appData.noGUI) {
4776
4777       if (appData.premove)
4778           if (!gotPremove ||
4779              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4780              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4781               ClearPremoveHighlights();
4782
4783       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4784         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4785       DrawPosition(j, boards[currentMove]);
4786
4787       DisplayMove(moveNum - 1);
4788       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4789             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4790               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4791         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4792       }
4793     }
4794
4795     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4796 #if ZIPPY
4797     if(bookHit) { // [HGM] book: simulate book reply
4798         static char bookMove[MSG_SIZ]; // a bit generous?
4799
4800         programStats.nodes = programStats.depth = programStats.time =
4801         programStats.score = programStats.got_only_move = 0;
4802         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4803
4804         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4805         strcat(bookMove, bookHit);
4806         HandleMachineMove(bookMove, &first);
4807     }
4808 #endif
4809 }
4810
4811 void
4812 GetMoveListEvent ()
4813 {
4814     char buf[MSG_SIZ];
4815     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4816         ics_getting_history = H_REQUESTED;
4817         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4818         SendToICS(buf);
4819     }
4820 }
4821
4822 void
4823 AnalysisPeriodicEvent (int force)
4824 {
4825     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4826          && !force) || !appData.periodicUpdates)
4827       return;
4828
4829     /* Send . command to Crafty to collect stats */
4830     SendToProgram(".\n", &first);
4831
4832     /* Don't send another until we get a response (this makes
4833        us stop sending to old Crafty's which don't understand
4834        the "." command (sending illegal cmds resets node count & time,
4835        which looks bad)) */
4836     programStats.ok_to_send = 0;
4837 }
4838
4839 void
4840 ics_update_width (int new_width)
4841 {
4842         ics_printf("set width %d\n", new_width);
4843 }
4844
4845 void
4846 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4847 {
4848     char buf[MSG_SIZ];
4849
4850     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4851         // null move in variant where engine does not understand it (for analysis purposes)
4852         SendBoard(cps, moveNum + 1); // send position after move in stead.
4853         return;
4854     }
4855     if (cps->useUsermove) {
4856       SendToProgram("usermove ", cps);
4857     }
4858     if (cps->useSAN) {
4859       char *space;
4860       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4861         int len = space - parseList[moveNum];
4862         memcpy(buf, parseList[moveNum], len);
4863         buf[len++] = '\n';
4864         buf[len] = NULLCHAR;
4865       } else {
4866         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4867       }
4868       SendToProgram(buf, cps);
4869     } else {
4870       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4871         AlphaRank(moveList[moveNum], 4);
4872         SendToProgram(moveList[moveNum], cps);
4873         AlphaRank(moveList[moveNum], 4); // and back
4874       } else
4875       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4876        * the engine. It would be nice to have a better way to identify castle
4877        * moves here. */
4878       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4879                                                                          && cps->useOOCastle) {
4880         int fromX = moveList[moveNum][0] - AAA;
4881         int fromY = moveList[moveNum][1] - ONE;
4882         int toX = moveList[moveNum][2] - AAA;
4883         int toY = moveList[moveNum][3] - ONE;
4884         if((boards[moveNum][fromY][fromX] == WhiteKing
4885             && boards[moveNum][toY][toX] == WhiteRook)
4886            || (boards[moveNum][fromY][fromX] == BlackKing
4887                && boards[moveNum][toY][toX] == BlackRook)) {
4888           if(toX > fromX) SendToProgram("O-O\n", cps);
4889           else SendToProgram("O-O-O\n", cps);
4890         }
4891         else SendToProgram(moveList[moveNum], cps);
4892       } else
4893       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4894         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4895           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4896           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4897                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4898         } else
4899           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4900                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4901         SendToProgram(buf, cps);
4902       }
4903       else SendToProgram(moveList[moveNum], cps);
4904       /* End of additions by Tord */
4905     }
4906
4907     /* [HGM] setting up the opening has brought engine in force mode! */
4908     /*       Send 'go' if we are in a mode where machine should play. */
4909     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4910         (gameMode == TwoMachinesPlay   ||
4911 #if ZIPPY
4912          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4913 #endif
4914          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4915         SendToProgram("go\n", cps);
4916   if (appData.debugMode) {
4917     fprintf(debugFP, "(extra)\n");
4918   }
4919     }
4920     setboardSpoiledMachineBlack = 0;
4921 }
4922
4923 void
4924 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4925 {
4926     char user_move[MSG_SIZ];
4927     char suffix[4];
4928
4929     if(gameInfo.variant == VariantSChess && promoChar) {
4930         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4931         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4932     } else suffix[0] = NULLCHAR;
4933
4934     switch (moveType) {
4935       default:
4936         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4937                 (int)moveType, fromX, fromY, toX, toY);
4938         DisplayError(user_move + strlen("say "), 0);
4939         break;
4940       case WhiteKingSideCastle:
4941       case BlackKingSideCastle:
4942       case WhiteQueenSideCastleWild:
4943       case BlackQueenSideCastleWild:
4944       /* PUSH Fabien */
4945       case WhiteHSideCastleFR:
4946       case BlackHSideCastleFR:
4947       /* POP Fabien */
4948         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4949         break;
4950       case WhiteQueenSideCastle:
4951       case BlackQueenSideCastle:
4952       case WhiteKingSideCastleWild:
4953       case BlackKingSideCastleWild:
4954       /* PUSH Fabien */
4955       case WhiteASideCastleFR:
4956       case BlackASideCastleFR:
4957       /* POP Fabien */
4958         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
4959         break;
4960       case WhiteNonPromotion:
4961       case BlackNonPromotion:
4962         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4963         break;
4964       case WhitePromotion:
4965       case BlackPromotion:
4966         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4967           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4968                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4969                 PieceToChar(WhiteFerz));
4970         else if(gameInfo.variant == VariantGreat)
4971           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4972                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4973                 PieceToChar(WhiteMan));
4974         else
4975           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4976                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4977                 promoChar);
4978         break;
4979       case WhiteDrop:
4980       case BlackDrop:
4981       drop:
4982         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4983                  ToUpper(PieceToChar((ChessSquare) fromX)),
4984                  AAA + toX, ONE + toY);
4985         break;
4986       case IllegalMove:  /* could be a variant we don't quite understand */
4987         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4988       case NormalMove:
4989       case WhiteCapturesEnPassant:
4990       case BlackCapturesEnPassant:
4991         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4992                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4993         break;
4994     }
4995     SendToICS(user_move);
4996     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4997         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4998 }
4999
5000 void
5001 UploadGameEvent ()
5002 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5003     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5004     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5005     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5006       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5007       return;
5008     }
5009     if(gameMode != IcsExamining) { // is this ever not the case?
5010         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5011
5012         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5013           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5014         } else { // on FICS we must first go to general examine mode
5015           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5016         }
5017         if(gameInfo.variant != VariantNormal) {
5018             // try figure out wild number, as xboard names are not always valid on ICS
5019             for(i=1; i<=36; i++) {
5020               snprintf(buf, MSG_SIZ, "wild/%d", i);
5021                 if(StringToVariant(buf) == gameInfo.variant) break;
5022             }
5023             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5024             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5025             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5026         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5027         SendToICS(ics_prefix);
5028         SendToICS(buf);
5029         if(startedFromSetupPosition || backwardMostMove != 0) {
5030           fen = PositionToFEN(backwardMostMove, NULL);
5031           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5032             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5033             SendToICS(buf);
5034           } else { // FICS: everything has to set by separate bsetup commands
5035             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5036             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5037             SendToICS(buf);
5038             if(!WhiteOnMove(backwardMostMove)) {
5039                 SendToICS("bsetup tomove black\n");
5040             }
5041             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5042             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5043             SendToICS(buf);
5044             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5045             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5046             SendToICS(buf);
5047             i = boards[backwardMostMove][EP_STATUS];
5048             if(i >= 0) { // set e.p.
5049               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5050                 SendToICS(buf);
5051             }
5052             bsetup++;
5053           }
5054         }
5055       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5056             SendToICS("bsetup done\n"); // switch to normal examining.
5057     }
5058     for(i = backwardMostMove; i<last; i++) {
5059         char buf[20];
5060         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5061         SendToICS(buf);
5062     }
5063     SendToICS(ics_prefix);
5064     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5065 }
5066
5067 void
5068 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5069 {
5070     if (rf == DROP_RANK) {
5071       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5072       sprintf(move, "%c@%c%c\n",
5073                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5074     } else {
5075         if (promoChar == 'x' || promoChar == NULLCHAR) {
5076           sprintf(move, "%c%c%c%c\n",
5077                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5078         } else {
5079             sprintf(move, "%c%c%c%c%c\n",
5080                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5081         }
5082     }
5083 }
5084
5085 void
5086 ProcessICSInitScript (FILE *f)
5087 {
5088     char buf[MSG_SIZ];
5089
5090     while (fgets(buf, MSG_SIZ, f)) {
5091         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5092     }
5093
5094     fclose(f);
5095 }
5096
5097
5098 static int lastX, lastY, selectFlag, dragging;
5099
5100 void
5101 Sweep (int step)
5102 {
5103     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5104     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5105     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5106     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5107     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5108     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5109     do {
5110         promoSweep -= step;
5111         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5112         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5113         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5114         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5115         if(!step) step = -1;
5116     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5117             appData.testLegality && (promoSweep == king ||
5118             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5119     ChangeDragPiece(promoSweep);
5120 }
5121
5122 int
5123 PromoScroll (int x, int y)
5124 {
5125   int step = 0;
5126
5127   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5128   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5129   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5130   if(!step) return FALSE;
5131   lastX = x; lastY = y;
5132   if((promoSweep < BlackPawn) == flipView) step = -step;
5133   if(step > 0) selectFlag = 1;
5134   if(!selectFlag) Sweep(step);
5135   return FALSE;
5136 }
5137
5138 void
5139 NextPiece (int step)
5140 {
5141     ChessSquare piece = boards[currentMove][toY][toX];
5142     do {
5143         pieceSweep -= step;
5144         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5145         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5146         if(!step) step = -1;
5147     } while(PieceToChar(pieceSweep) == '.');
5148     boards[currentMove][toY][toX] = pieceSweep;
5149     DrawPosition(FALSE, boards[currentMove]);
5150     boards[currentMove][toY][toX] = piece;
5151 }
5152 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5153 void
5154 AlphaRank (char *move, int n)
5155 {
5156 //    char *p = move, c; int x, y;
5157
5158     if (appData.debugMode) {
5159         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5160     }
5161
5162     if(move[1]=='*' &&
5163        move[2]>='0' && move[2]<='9' &&
5164        move[3]>='a' && move[3]<='x'    ) {
5165         move[1] = '@';
5166         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5167         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5168     } else
5169     if(move[0]>='0' && move[0]<='9' &&
5170        move[1]>='a' && move[1]<='x' &&
5171        move[2]>='0' && move[2]<='9' &&
5172        move[3]>='a' && move[3]<='x'    ) {
5173         /* input move, Shogi -> normal */
5174         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5175         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5176         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5177         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5178     } else
5179     if(move[1]=='@' &&
5180        move[3]>='0' && move[3]<='9' &&
5181        move[2]>='a' && move[2]<='x'    ) {
5182         move[1] = '*';
5183         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5184         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5185     } else
5186     if(
5187        move[0]>='a' && move[0]<='x' &&
5188        move[3]>='0' && move[3]<='9' &&
5189        move[2]>='a' && move[2]<='x'    ) {
5190          /* output move, normal -> Shogi */
5191         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5192         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5193         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5194         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5195         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5196     }
5197     if (appData.debugMode) {
5198         fprintf(debugFP, "   out = '%s'\n", move);
5199     }
5200 }
5201
5202 char yy_textstr[8000];
5203
5204 /* Parser for moves from gnuchess, ICS, or user typein box */
5205 Boolean
5206 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5207 {
5208     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5209
5210     switch (*moveType) {
5211       case WhitePromotion:
5212       case BlackPromotion:
5213       case WhiteNonPromotion:
5214       case BlackNonPromotion:
5215       case NormalMove:
5216       case WhiteCapturesEnPassant:
5217       case BlackCapturesEnPassant:
5218       case WhiteKingSideCastle:
5219       case WhiteQueenSideCastle:
5220       case BlackKingSideCastle:
5221       case BlackQueenSideCastle:
5222       case WhiteKingSideCastleWild:
5223       case WhiteQueenSideCastleWild:
5224       case BlackKingSideCastleWild:
5225       case BlackQueenSideCastleWild:
5226       /* Code added by Tord: */
5227       case WhiteHSideCastleFR:
5228       case WhiteASideCastleFR:
5229       case BlackHSideCastleFR:
5230       case BlackASideCastleFR:
5231       /* End of code added by Tord */
5232       case IllegalMove:         /* bug or odd chess variant */
5233         *fromX = currentMoveString[0] - AAA;
5234         *fromY = currentMoveString[1] - ONE;
5235         *toX = currentMoveString[2] - AAA;
5236         *toY = currentMoveString[3] - ONE;
5237         *promoChar = currentMoveString[4];
5238         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5239             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5240     if (appData.debugMode) {
5241         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5242     }
5243             *fromX = *fromY = *toX = *toY = 0;
5244             return FALSE;
5245         }
5246         if (appData.testLegality) {
5247           return (*moveType != IllegalMove);
5248         } else {
5249           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5250                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5251         }
5252
5253       case WhiteDrop:
5254       case BlackDrop:
5255         *fromX = *moveType == WhiteDrop ?
5256           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5257           (int) CharToPiece(ToLower(currentMoveString[0]));
5258         *fromY = DROP_RANK;
5259         *toX = currentMoveString[2] - AAA;
5260         *toY = currentMoveString[3] - ONE;
5261         *promoChar = NULLCHAR;
5262         return TRUE;
5263
5264       case AmbiguousMove:
5265       case ImpossibleMove:
5266       case EndOfFile:
5267       case ElapsedTime:
5268       case Comment:
5269       case PGNTag:
5270       case NAG:
5271       case WhiteWins:
5272       case BlackWins:
5273       case GameIsDrawn:
5274       default:
5275     if (appData.debugMode) {
5276         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5277     }
5278         /* bug? */
5279         *fromX = *fromY = *toX = *toY = 0;
5280         *promoChar = NULLCHAR;
5281         return FALSE;
5282     }
5283 }
5284
5285 Boolean pushed = FALSE;
5286 char *lastParseAttempt;
5287
5288 void
5289 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5290 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5291   int fromX, fromY, toX, toY; char promoChar;
5292   ChessMove moveType;
5293   Boolean valid;
5294   int nr = 0;
5295
5296   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5297     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5298     pushed = TRUE;
5299   }
5300   endPV = forwardMostMove;
5301   do {
5302     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5303     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5304     lastParseAttempt = pv;
5305     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5306 if(appData.debugMode){
5307 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);
5308 }
5309     if(!valid && nr == 0 &&
5310        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5311         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5312         // Hande case where played move is different from leading PV move
5313         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5314         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5315         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5316         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5317           endPV += 2; // if position different, keep this
5318           moveList[endPV-1][0] = fromX + AAA;
5319           moveList[endPV-1][1] = fromY + ONE;
5320           moveList[endPV-1][2] = toX + AAA;
5321           moveList[endPV-1][3] = toY + ONE;
5322           parseList[endPV-1][0] = NULLCHAR;
5323           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5324         }
5325       }
5326     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5327     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5328     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5329     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5330         valid++; // allow comments in PV
5331         continue;
5332     }
5333     nr++;
5334     if(endPV+1 > framePtr) break; // no space, truncate
5335     if(!valid) break;
5336     endPV++;
5337     CopyBoard(boards[endPV], boards[endPV-1]);
5338     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5339     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5340     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5341     CoordsToAlgebraic(boards[endPV - 1],
5342                              PosFlags(endPV - 1),
5343                              fromY, fromX, toY, toX, promoChar,
5344                              parseList[endPV - 1]);
5345   } while(valid);
5346   if(atEnd == 2) return; // used hidden, for PV conversion
5347   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5348   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5349   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5350                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5351   DrawPosition(TRUE, boards[currentMove]);
5352 }
5353
5354 int
5355 MultiPV (ChessProgramState *cps)
5356 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5357         int i;
5358         for(i=0; i<cps->nrOptions; i++)
5359             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5360                 return i;
5361         return -1;
5362 }
5363
5364 Boolean
5365 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5366 {
5367         int startPV, multi, lineStart, origIndex = index;
5368         char *p, buf2[MSG_SIZ];
5369
5370         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5371         lastX = x; lastY = y;
5372         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5373         lineStart = startPV = index;
5374         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5375         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5376         index = startPV;
5377         do{ while(buf[index] && buf[index] != '\n') index++;
5378         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5379         buf[index] = 0;
5380         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5381                 int n = first.option[multi].value;
5382                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5383                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5384                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5385                 first.option[multi].value = n;
5386                 *start = *end = 0;
5387                 return FALSE;
5388         }
5389         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5390         *start = startPV; *end = index-1;
5391         return TRUE;
5392 }
5393
5394 char *
5395 PvToSAN (char *pv)
5396 {
5397         static char buf[10*MSG_SIZ];
5398         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5399         *buf = NULLCHAR;
5400         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5401         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5402         for(i = forwardMostMove; i<endPV; i++){
5403             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5404             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5405             k += strlen(buf+k);
5406         }
5407         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5408         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5409         endPV = savedEnd;
5410         return buf;
5411 }
5412
5413 Boolean
5414 LoadPV (int x, int y)
5415 { // called on right mouse click to load PV
5416   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5417   lastX = x; lastY = y;
5418   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5419   return TRUE;
5420 }
5421
5422 void
5423 UnLoadPV ()
5424 {
5425   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5426   if(endPV < 0) return;
5427   endPV = -1;
5428   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5429         Boolean saveAnimate = appData.animate;
5430         if(pushed) {
5431             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5432                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5433             } else storedGames--; // abandon shelved tail of original game
5434         }
5435         pushed = FALSE;
5436         forwardMostMove = currentMove;
5437         currentMove = oldFMM;
5438         appData.animate = FALSE;
5439         ToNrEvent(forwardMostMove);
5440         appData.animate = saveAnimate;
5441   }
5442   currentMove = forwardMostMove;
5443   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5444   ClearPremoveHighlights();
5445   DrawPosition(TRUE, boards[currentMove]);
5446 }
5447
5448 void
5449 MovePV (int x, int y, int h)
5450 { // step through PV based on mouse coordinates (called on mouse move)
5451   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5452
5453   // we must somehow check if right button is still down (might be released off board!)
5454   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5455   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5456   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5457   if(!step) return;
5458   lastX = x; lastY = y;
5459
5460   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5461   if(endPV < 0) return;
5462   if(y < margin) step = 1; else
5463   if(y > h - margin) step = -1;
5464   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5465   currentMove += step;
5466   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5467   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5468                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5469   DrawPosition(FALSE, boards[currentMove]);
5470 }
5471
5472
5473 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5474 // All positions will have equal probability, but the current method will not provide a unique
5475 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5476 #define DARK 1
5477 #define LITE 2
5478 #define ANY 3
5479
5480 int squaresLeft[4];
5481 int piecesLeft[(int)BlackPawn];
5482 int seed, nrOfShuffles;
5483
5484 void
5485 GetPositionNumber ()
5486 {       // sets global variable seed
5487         int i;
5488
5489         seed = appData.defaultFrcPosition;
5490         if(seed < 0) { // randomize based on time for negative FRC position numbers
5491                 for(i=0; i<50; i++) seed += random();
5492                 seed = random() ^ random() >> 8 ^ random() << 8;
5493                 if(seed<0) seed = -seed;
5494         }
5495 }
5496
5497 int
5498 put (Board board, int pieceType, int rank, int n, int shade)
5499 // put the piece on the (n-1)-th empty squares of the given shade
5500 {
5501         int i;
5502
5503         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5504                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5505                         board[rank][i] = (ChessSquare) pieceType;
5506                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5507                         squaresLeft[ANY]--;
5508                         piecesLeft[pieceType]--;
5509                         return i;
5510                 }
5511         }
5512         return -1;
5513 }
5514
5515
5516 void
5517 AddOnePiece (Board board, int pieceType, int rank, int shade)
5518 // calculate where the next piece goes, (any empty square), and put it there
5519 {
5520         int i;
5521
5522         i = seed % squaresLeft[shade];
5523         nrOfShuffles *= squaresLeft[shade];
5524         seed /= squaresLeft[shade];
5525         put(board, pieceType, rank, i, shade);
5526 }
5527
5528 void
5529 AddTwoPieces (Board board, int pieceType, int rank)
5530 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5531 {
5532         int i, n=squaresLeft[ANY], j=n-1, k;
5533
5534         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5535         i = seed % k;  // pick one
5536         nrOfShuffles *= k;
5537         seed /= k;
5538         while(i >= j) i -= j--;
5539         j = n - 1 - j; i += j;
5540         put(board, pieceType, rank, j, ANY);
5541         put(board, pieceType, rank, i, ANY);
5542 }
5543
5544 void
5545 SetUpShuffle (Board board, int number)
5546 {
5547         int i, p, first=1;
5548
5549         GetPositionNumber(); nrOfShuffles = 1;
5550
5551         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5552         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5553         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5554
5555         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5556
5557         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5558             p = (int) board[0][i];
5559             if(p < (int) BlackPawn) piecesLeft[p] ++;
5560             board[0][i] = EmptySquare;
5561         }
5562
5563         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5564             // shuffles restricted to allow normal castling put KRR first
5565             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5566                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5567             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5568                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5569             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5570                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5571             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5572                 put(board, WhiteRook, 0, 0, ANY);
5573             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5574         }
5575
5576         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5577             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5578             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5579                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5580                 while(piecesLeft[p] >= 2) {
5581                     AddOnePiece(board, p, 0, LITE);
5582                     AddOnePiece(board, p, 0, DARK);
5583                 }
5584                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5585             }
5586
5587         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5588             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5589             // but we leave King and Rooks for last, to possibly obey FRC restriction
5590             if(p == (int)WhiteRook) continue;
5591             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5592             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5593         }
5594
5595         // now everything is placed, except perhaps King (Unicorn) and Rooks
5596
5597         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5598             // Last King gets castling rights
5599             while(piecesLeft[(int)WhiteUnicorn]) {
5600                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5601                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5602             }
5603
5604             while(piecesLeft[(int)WhiteKing]) {
5605                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5606                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5607             }
5608
5609
5610         } else {
5611             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5612             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5613         }
5614
5615         // Only Rooks can be left; simply place them all
5616         while(piecesLeft[(int)WhiteRook]) {
5617                 i = put(board, WhiteRook, 0, 0, ANY);
5618                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5619                         if(first) {
5620                                 first=0;
5621                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5622                         }
5623                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5624                 }
5625         }
5626         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5627             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5628         }
5629
5630         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5631 }
5632
5633 int
5634 SetCharTable (char *table, const char * map)
5635 /* [HGM] moved here from winboard.c because of its general usefulness */
5636 /*       Basically a safe strcpy that uses the last character as King */
5637 {
5638     int result = FALSE; int NrPieces;
5639
5640     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5641                     && NrPieces >= 12 && !(NrPieces&1)) {
5642         int i; /* [HGM] Accept even length from 12 to 34 */
5643
5644         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5645         for( i=0; i<NrPieces/2-1; i++ ) {
5646             table[i] = map[i];
5647             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5648         }
5649         table[(int) WhiteKing]  = map[NrPieces/2-1];
5650         table[(int) BlackKing]  = map[NrPieces-1];
5651
5652         result = TRUE;
5653     }
5654
5655     return result;
5656 }
5657
5658 void
5659 Prelude (Board board)
5660 {       // [HGM] superchess: random selection of exo-pieces
5661         int i, j, k; ChessSquare p;
5662         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5663
5664         GetPositionNumber(); // use FRC position number
5665
5666         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5667             SetCharTable(pieceToChar, appData.pieceToCharTable);
5668             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5669                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5670         }
5671
5672         j = seed%4;                 seed /= 4;
5673         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5674         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5675         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5676         j = seed%3 + (seed%3 >= j); seed /= 3;
5677         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5678         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5679         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5680         j = seed%3;                 seed /= 3;
5681         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5682         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5683         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5684         j = seed%2 + (seed%2 >= j); seed /= 2;
5685         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5686         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5687         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5688         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5689         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5690         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5691         put(board, exoPieces[0],    0, 0, ANY);
5692         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5693 }
5694
5695 void
5696 InitPosition (int redraw)
5697 {
5698     ChessSquare (* pieces)[BOARD_FILES];
5699     int i, j, pawnRow, overrule,
5700     oldx = gameInfo.boardWidth,
5701     oldy = gameInfo.boardHeight,
5702     oldh = gameInfo.holdingsWidth;
5703     static int oldv;
5704
5705     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5706
5707     /* [AS] Initialize pv info list [HGM] and game status */
5708     {
5709         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5710             pvInfoList[i].depth = 0;
5711             boards[i][EP_STATUS] = EP_NONE;
5712             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5713         }
5714
5715         initialRulePlies = 0; /* 50-move counter start */
5716
5717         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5718         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5719     }
5720
5721
5722     /* [HGM] logic here is completely changed. In stead of full positions */
5723     /* the initialized data only consist of the two backranks. The switch */
5724     /* selects which one we will use, which is than copied to the Board   */
5725     /* initialPosition, which for the rest is initialized by Pawns and    */
5726     /* empty squares. This initial position is then copied to boards[0],  */
5727     /* possibly after shuffling, so that it remains available.            */
5728
5729     gameInfo.holdingsWidth = 0; /* default board sizes */
5730     gameInfo.boardWidth    = 8;
5731     gameInfo.boardHeight   = 8;
5732     gameInfo.holdingsSize  = 0;
5733     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5734     for(i=0; i<BOARD_FILES-2; i++)
5735       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5736     initialPosition[EP_STATUS] = EP_NONE;
5737     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5738     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5739          SetCharTable(pieceNickName, appData.pieceNickNames);
5740     else SetCharTable(pieceNickName, "............");
5741     pieces = FIDEArray;
5742
5743     switch (gameInfo.variant) {
5744     case VariantFischeRandom:
5745       shuffleOpenings = TRUE;
5746     default:
5747       break;
5748     case VariantShatranj:
5749       pieces = ShatranjArray;
5750       nrCastlingRights = 0;
5751       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5752       break;
5753     case VariantMakruk:
5754       pieces = makrukArray;
5755       nrCastlingRights = 0;
5756       startedFromSetupPosition = TRUE;
5757       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5758       break;
5759     case VariantTwoKings:
5760       pieces = twoKingsArray;
5761       break;
5762     case VariantGrand:
5763       pieces = GrandArray;
5764       nrCastlingRights = 0;
5765       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5766       gameInfo.boardWidth = 10;
5767       gameInfo.boardHeight = 10;
5768       gameInfo.holdingsSize = 7;
5769       break;
5770     case VariantCapaRandom:
5771       shuffleOpenings = TRUE;
5772     case VariantCapablanca:
5773       pieces = CapablancaArray;
5774       gameInfo.boardWidth = 10;
5775       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5776       break;
5777     case VariantGothic:
5778       pieces = GothicArray;
5779       gameInfo.boardWidth = 10;
5780       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5781       break;
5782     case VariantSChess:
5783       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5784       gameInfo.holdingsSize = 7;
5785       break;
5786     case VariantJanus:
5787       pieces = JanusArray;
5788       gameInfo.boardWidth = 10;
5789       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5790       nrCastlingRights = 6;
5791         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5792         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5793         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5794         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5795         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5796         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5797       break;
5798     case VariantFalcon:
5799       pieces = FalconArray;
5800       gameInfo.boardWidth = 10;
5801       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5802       break;
5803     case VariantXiangqi:
5804       pieces = XiangqiArray;
5805       gameInfo.boardWidth  = 9;
5806       gameInfo.boardHeight = 10;
5807       nrCastlingRights = 0;
5808       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5809       break;
5810     case VariantShogi:
5811       pieces = ShogiArray;
5812       gameInfo.boardWidth  = 9;
5813       gameInfo.boardHeight = 9;
5814       gameInfo.holdingsSize = 7;
5815       nrCastlingRights = 0;
5816       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5817       break;
5818     case VariantCourier:
5819       pieces = CourierArray;
5820       gameInfo.boardWidth  = 12;
5821       nrCastlingRights = 0;
5822       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5823       break;
5824     case VariantKnightmate:
5825       pieces = KnightmateArray;
5826       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5827       break;
5828     case VariantSpartan:
5829       pieces = SpartanArray;
5830       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5831       break;
5832     case VariantFairy:
5833       pieces = fairyArray;
5834       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5835       break;
5836     case VariantGreat:
5837       pieces = GreatArray;
5838       gameInfo.boardWidth = 10;
5839       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5840       gameInfo.holdingsSize = 8;
5841       break;
5842     case VariantSuper:
5843       pieces = FIDEArray;
5844       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5845       gameInfo.holdingsSize = 8;
5846       startedFromSetupPosition = TRUE;
5847       break;
5848     case VariantCrazyhouse:
5849     case VariantBughouse:
5850       pieces = FIDEArray;
5851       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5852       gameInfo.holdingsSize = 5;
5853       break;
5854     case VariantWildCastle:
5855       pieces = FIDEArray;
5856       /* !!?shuffle with kings guaranteed to be on d or e file */
5857       shuffleOpenings = 1;
5858       break;
5859     case VariantNoCastle:
5860       pieces = FIDEArray;
5861       nrCastlingRights = 0;
5862       /* !!?unconstrained back-rank shuffle */
5863       shuffleOpenings = 1;
5864       break;
5865     }
5866
5867     overrule = 0;
5868     if(appData.NrFiles >= 0) {
5869         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5870         gameInfo.boardWidth = appData.NrFiles;
5871     }
5872     if(appData.NrRanks >= 0) {
5873         gameInfo.boardHeight = appData.NrRanks;
5874     }
5875     if(appData.holdingsSize >= 0) {
5876         i = appData.holdingsSize;
5877         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5878         gameInfo.holdingsSize = i;
5879     }
5880     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5881     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5882         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5883
5884     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5885     if(pawnRow < 1) pawnRow = 1;
5886     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5887
5888     /* User pieceToChar list overrules defaults */
5889     if(appData.pieceToCharTable != NULL)
5890         SetCharTable(pieceToChar, appData.pieceToCharTable);
5891
5892     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5893
5894         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5895             s = (ChessSquare) 0; /* account holding counts in guard band */
5896         for( i=0; i<BOARD_HEIGHT; i++ )
5897             initialPosition[i][j] = s;
5898
5899         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5900         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5901         initialPosition[pawnRow][j] = WhitePawn;
5902         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5903         if(gameInfo.variant == VariantXiangqi) {
5904             if(j&1) {
5905                 initialPosition[pawnRow][j] =
5906                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5907                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5908                    initialPosition[2][j] = WhiteCannon;
5909                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5910                 }
5911             }
5912         }
5913         if(gameInfo.variant == VariantGrand) {
5914             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5915                initialPosition[0][j] = WhiteRook;
5916                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5917             }
5918         }
5919         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5920     }
5921     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5922
5923             j=BOARD_LEFT+1;
5924             initialPosition[1][j] = WhiteBishop;
5925             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5926             j=BOARD_RGHT-2;
5927             initialPosition[1][j] = WhiteRook;
5928             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5929     }
5930
5931     if( nrCastlingRights == -1) {
5932         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5933         /*       This sets default castling rights from none to normal corners   */
5934         /* Variants with other castling rights must set them themselves above    */
5935         nrCastlingRights = 6;
5936
5937         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5938         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5939         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5940         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5941         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5942         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5943      }
5944
5945      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5946      if(gameInfo.variant == VariantGreat) { // promotion commoners
5947         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5948         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5949         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5950         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5951      }
5952      if( gameInfo.variant == VariantSChess ) {
5953       initialPosition[1][0] = BlackMarshall;
5954       initialPosition[2][0] = BlackAngel;
5955       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5956       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5957       initialPosition[1][1] = initialPosition[2][1] = 
5958       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5959      }
5960   if (appData.debugMode) {
5961     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5962   }
5963     if(shuffleOpenings) {
5964         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5965         startedFromSetupPosition = TRUE;
5966     }
5967     if(startedFromPositionFile) {
5968       /* [HGM] loadPos: use PositionFile for every new game */
5969       CopyBoard(initialPosition, filePosition);
5970       for(i=0; i<nrCastlingRights; i++)
5971           initialRights[i] = filePosition[CASTLING][i];
5972       startedFromSetupPosition = TRUE;
5973     }
5974
5975     CopyBoard(boards[0], initialPosition);
5976
5977     if(oldx != gameInfo.boardWidth ||
5978        oldy != gameInfo.boardHeight ||
5979        oldv != gameInfo.variant ||
5980        oldh != gameInfo.holdingsWidth
5981                                          )
5982             InitDrawingSizes(-2 ,0);
5983
5984     oldv = gameInfo.variant;
5985     if (redraw)
5986       DrawPosition(TRUE, boards[currentMove]);
5987 }
5988
5989 void
5990 SendBoard (ChessProgramState *cps, int moveNum)
5991 {
5992     char message[MSG_SIZ];
5993
5994     if (cps->useSetboard) {
5995       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5996       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5997       SendToProgram(message, cps);
5998       free(fen);
5999
6000     } else {
6001       ChessSquare *bp;
6002       int i, j, left=0, right=BOARD_WIDTH;
6003       /* Kludge to set black to move, avoiding the troublesome and now
6004        * deprecated "black" command.
6005        */
6006       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6007         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6008
6009       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6010
6011       SendToProgram("edit\n", cps);
6012       SendToProgram("#\n", cps);
6013       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6014         bp = &boards[moveNum][i][left];
6015         for (j = left; j < right; j++, bp++) {
6016           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6017           if ((int) *bp < (int) BlackPawn) {
6018             if(j == BOARD_RGHT+1)
6019                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6020             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6021             if(message[0] == '+' || message[0] == '~') {
6022               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6023                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6024                         AAA + j, ONE + i);
6025             }
6026             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6027                 message[1] = BOARD_RGHT   - 1 - j + '1';
6028                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6029             }
6030             SendToProgram(message, cps);
6031           }
6032         }
6033       }
6034
6035       SendToProgram("c\n", cps);
6036       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6037         bp = &boards[moveNum][i][left];
6038         for (j = left; j < right; j++, bp++) {
6039           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6040           if (((int) *bp != (int) EmptySquare)
6041               && ((int) *bp >= (int) BlackPawn)) {
6042             if(j == BOARD_LEFT-2)
6043                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6044             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6045                     AAA + j, ONE + i);
6046             if(message[0] == '+' || message[0] == '~') {
6047               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6048                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6049                         AAA + j, ONE + i);
6050             }
6051             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6052                 message[1] = BOARD_RGHT   - 1 - j + '1';
6053                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6054             }
6055             SendToProgram(message, cps);
6056           }
6057         }
6058       }
6059
6060       SendToProgram(".\n", cps);
6061     }
6062     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6063 }
6064
6065 ChessSquare
6066 DefaultPromoChoice (int white)
6067 {
6068     ChessSquare result;
6069     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6070         result = WhiteFerz; // no choice
6071     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6072         result= WhiteKing; // in Suicide Q is the last thing we want
6073     else if(gameInfo.variant == VariantSpartan)
6074         result = white ? WhiteQueen : WhiteAngel;
6075     else result = WhiteQueen;
6076     if(!white) result = WHITE_TO_BLACK result;
6077     return result;
6078 }
6079
6080 static int autoQueen; // [HGM] oneclick
6081
6082 int
6083 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6084 {
6085     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6086     /* [HGM] add Shogi promotions */
6087     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6088     ChessSquare piece;
6089     ChessMove moveType;
6090     Boolean premove;
6091
6092     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6093     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6094
6095     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6096       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6097         return FALSE;
6098
6099     piece = boards[currentMove][fromY][fromX];
6100     if(gameInfo.variant == VariantShogi) {
6101         promotionZoneSize = BOARD_HEIGHT/3;
6102         highestPromotingPiece = (int)WhiteFerz;
6103     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6104         promotionZoneSize = 3;
6105     }
6106
6107     // Treat Lance as Pawn when it is not representing Amazon
6108     if(gameInfo.variant != VariantSuper) {
6109         if(piece == WhiteLance) piece = WhitePawn; else
6110         if(piece == BlackLance) piece = BlackPawn;
6111     }
6112
6113     // next weed out all moves that do not touch the promotion zone at all
6114     if((int)piece >= BlackPawn) {
6115         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6116              return FALSE;
6117         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6118     } else {
6119         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6120            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6121     }
6122
6123     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6124
6125     // weed out mandatory Shogi promotions
6126     if(gameInfo.variant == VariantShogi) {
6127         if(piece >= BlackPawn) {
6128             if(toY == 0 && piece == BlackPawn ||
6129                toY == 0 && piece == BlackQueen ||
6130                toY <= 1 && piece == BlackKnight) {
6131                 *promoChoice = '+';
6132                 return FALSE;
6133             }
6134         } else {
6135             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6136                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6137                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6138                 *promoChoice = '+';
6139                 return FALSE;
6140             }
6141         }
6142     }
6143
6144     // weed out obviously illegal Pawn moves
6145     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6146         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6147         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6148         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6149         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6150         // note we are not allowed to test for valid (non-)capture, due to premove
6151     }
6152
6153     // we either have a choice what to promote to, or (in Shogi) whether to promote
6154     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6155         *promoChoice = PieceToChar(BlackFerz);  // no choice
6156         return FALSE;
6157     }
6158     // no sense asking what we must promote to if it is going to explode...
6159     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6160         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6161         return FALSE;
6162     }
6163     // give caller the default choice even if we will not make it
6164     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6165     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6166     if(        sweepSelect && gameInfo.variant != VariantGreat
6167                            && gameInfo.variant != VariantGrand
6168                            && gameInfo.variant != VariantSuper) return FALSE;
6169     if(autoQueen) return FALSE; // predetermined
6170
6171     // suppress promotion popup on illegal moves that are not premoves
6172     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6173               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6174     if(appData.testLegality && !premove) {
6175         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6176                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6177         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6178             return FALSE;
6179     }
6180
6181     return TRUE;
6182 }
6183
6184 int
6185 InPalace (int row, int column)
6186 {   /* [HGM] for Xiangqi */
6187     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6188          column < (BOARD_WIDTH + 4)/2 &&
6189          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6190     return FALSE;
6191 }
6192
6193 int
6194 PieceForSquare (int x, int y)
6195 {
6196   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6197      return -1;
6198   else
6199      return boards[currentMove][y][x];
6200 }
6201
6202 int
6203 OKToStartUserMove (int x, int y)
6204 {
6205     ChessSquare from_piece;
6206     int white_piece;
6207
6208     if (matchMode) return FALSE;
6209     if (gameMode == EditPosition) return TRUE;
6210
6211     if (x >= 0 && y >= 0)
6212       from_piece = boards[currentMove][y][x];
6213     else
6214       from_piece = EmptySquare;
6215
6216     if (from_piece == EmptySquare) return FALSE;
6217
6218     white_piece = (int)from_piece >= (int)WhitePawn &&
6219       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6220
6221     switch (gameMode) {
6222       case AnalyzeFile:
6223       case TwoMachinesPlay:
6224       case EndOfGame:
6225         return FALSE;
6226
6227       case IcsObserving:
6228       case IcsIdle:
6229         return FALSE;
6230
6231       case MachinePlaysWhite:
6232       case IcsPlayingBlack:
6233         if (appData.zippyPlay) return FALSE;
6234         if (white_piece) {
6235             DisplayMoveError(_("You are playing Black"));
6236             return FALSE;
6237         }
6238         break;
6239
6240       case MachinePlaysBlack:
6241       case IcsPlayingWhite:
6242         if (appData.zippyPlay) return FALSE;
6243         if (!white_piece) {
6244             DisplayMoveError(_("You are playing White"));
6245             return FALSE;
6246         }
6247         break;
6248
6249       case PlayFromGameFile:
6250             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6251       case EditGame:
6252         if (!white_piece && WhiteOnMove(currentMove)) {
6253             DisplayMoveError(_("It is White's turn"));
6254             return FALSE;
6255         }
6256         if (white_piece && !WhiteOnMove(currentMove)) {
6257             DisplayMoveError(_("It is Black's turn"));
6258             return FALSE;
6259         }
6260         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6261             /* Editing correspondence game history */
6262             /* Could disallow this or prompt for confirmation */
6263             cmailOldMove = -1;
6264         }
6265         break;
6266
6267       case BeginningOfGame:
6268         if (appData.icsActive) return FALSE;
6269         if (!appData.noChessProgram) {
6270             if (!white_piece) {
6271                 DisplayMoveError(_("You are playing White"));
6272                 return FALSE;
6273             }
6274         }
6275         break;
6276
6277       case Training:
6278         if (!white_piece && WhiteOnMove(currentMove)) {
6279             DisplayMoveError(_("It is White's turn"));
6280             return FALSE;
6281         }
6282         if (white_piece && !WhiteOnMove(currentMove)) {
6283             DisplayMoveError(_("It is Black's turn"));
6284             return FALSE;
6285         }
6286         break;
6287
6288       default:
6289       case IcsExamining:
6290         break;
6291     }
6292     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6293         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6294         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6295         && gameMode != AnalyzeFile && gameMode != Training) {
6296         DisplayMoveError(_("Displayed position is not current"));
6297         return FALSE;
6298     }
6299     return TRUE;
6300 }
6301
6302 Boolean
6303 OnlyMove (int *x, int *y, Boolean captures) 
6304 {
6305     DisambiguateClosure cl;
6306     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6307     switch(gameMode) {
6308       case MachinePlaysBlack:
6309       case IcsPlayingWhite:
6310       case BeginningOfGame:
6311         if(!WhiteOnMove(currentMove)) return FALSE;
6312         break;
6313       case MachinePlaysWhite:
6314       case IcsPlayingBlack:
6315         if(WhiteOnMove(currentMove)) return FALSE;
6316         break;
6317       case EditGame:
6318         break;
6319       default:
6320         return FALSE;
6321     }
6322     cl.pieceIn = EmptySquare;
6323     cl.rfIn = *y;
6324     cl.ffIn = *x;
6325     cl.rtIn = -1;
6326     cl.ftIn = -1;
6327     cl.promoCharIn = NULLCHAR;
6328     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6329     if( cl.kind == NormalMove ||
6330         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6331         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6332         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6333       fromX = cl.ff;
6334       fromY = cl.rf;
6335       *x = cl.ft;
6336       *y = cl.rt;
6337       return TRUE;
6338     }
6339     if(cl.kind != ImpossibleMove) return FALSE;
6340     cl.pieceIn = EmptySquare;
6341     cl.rfIn = -1;
6342     cl.ffIn = -1;
6343     cl.rtIn = *y;
6344     cl.ftIn = *x;
6345     cl.promoCharIn = NULLCHAR;
6346     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6347     if( cl.kind == NormalMove ||
6348         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6349         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6350         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6351       fromX = cl.ff;
6352       fromY = cl.rf;
6353       *x = cl.ft;
6354       *y = cl.rt;
6355       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6356       return TRUE;
6357     }
6358     return FALSE;
6359 }
6360
6361 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6362 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6363 int lastLoadGameUseList = FALSE;
6364 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6365 ChessMove lastLoadGameStart = EndOfFile;
6366
6367 void
6368 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6369 {
6370     ChessMove moveType;
6371     ChessSquare pdown, pup;
6372
6373     /* Check if the user is playing in turn.  This is complicated because we
6374        let the user "pick up" a piece before it is his turn.  So the piece he
6375        tried to pick up may have been captured by the time he puts it down!
6376        Therefore we use the color the user is supposed to be playing in this
6377        test, not the color of the piece that is currently on the starting
6378        square---except in EditGame mode, where the user is playing both
6379        sides; fortunately there the capture race can't happen.  (It can
6380        now happen in IcsExamining mode, but that's just too bad.  The user
6381        will get a somewhat confusing message in that case.)
6382        */
6383
6384     switch (gameMode) {
6385       case AnalyzeFile:
6386       case TwoMachinesPlay:
6387       case EndOfGame:
6388       case IcsObserving:
6389       case IcsIdle:
6390         /* We switched into a game mode where moves are not accepted,
6391            perhaps while the mouse button was down. */
6392         return;
6393
6394       case MachinePlaysWhite:
6395         /* User is moving for Black */
6396         if (WhiteOnMove(currentMove)) {
6397             DisplayMoveError(_("It is White's turn"));
6398             return;
6399         }
6400         break;
6401
6402       case MachinePlaysBlack:
6403         /* User is moving for White */
6404         if (!WhiteOnMove(currentMove)) {
6405             DisplayMoveError(_("It is Black's turn"));
6406             return;
6407         }
6408         break;
6409
6410       case PlayFromGameFile:
6411             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6412       case EditGame:
6413       case IcsExamining:
6414       case BeginningOfGame:
6415       case AnalyzeMode:
6416       case Training:
6417         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6418         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6419             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6420             /* User is moving for Black */
6421             if (WhiteOnMove(currentMove)) {
6422                 DisplayMoveError(_("It is White's turn"));
6423                 return;
6424             }
6425         } else {
6426             /* User is moving for White */
6427             if (!WhiteOnMove(currentMove)) {
6428                 DisplayMoveError(_("It is Black's turn"));
6429                 return;
6430             }
6431         }
6432         break;
6433
6434       case IcsPlayingBlack:
6435         /* User is moving for Black */
6436         if (WhiteOnMove(currentMove)) {
6437             if (!appData.premove) {
6438                 DisplayMoveError(_("It is White's turn"));
6439             } else if (toX >= 0 && toY >= 0) {
6440                 premoveToX = toX;
6441                 premoveToY = toY;
6442                 premoveFromX = fromX;
6443                 premoveFromY = fromY;
6444                 premovePromoChar = promoChar;
6445                 gotPremove = 1;
6446                 if (appData.debugMode)
6447                     fprintf(debugFP, "Got premove: fromX %d,"
6448                             "fromY %d, toX %d, toY %d\n",
6449                             fromX, fromY, toX, toY);
6450             }
6451             return;
6452         }
6453         break;
6454
6455       case IcsPlayingWhite:
6456         /* User is moving for White */
6457         if (!WhiteOnMove(currentMove)) {
6458             if (!appData.premove) {
6459                 DisplayMoveError(_("It is Black's turn"));
6460             } else if (toX >= 0 && toY >= 0) {
6461                 premoveToX = toX;
6462                 premoveToY = toY;
6463                 premoveFromX = fromX;
6464                 premoveFromY = fromY;
6465                 premovePromoChar = promoChar;
6466                 gotPremove = 1;
6467                 if (appData.debugMode)
6468                     fprintf(debugFP, "Got premove: fromX %d,"
6469                             "fromY %d, toX %d, toY %d\n",
6470                             fromX, fromY, toX, toY);
6471             }
6472             return;
6473         }
6474         break;
6475
6476       default:
6477         break;
6478
6479       case EditPosition:
6480         /* EditPosition, empty square, or different color piece;
6481            click-click move is possible */
6482         if (toX == -2 || toY == -2) {
6483             boards[0][fromY][fromX] = EmptySquare;
6484             DrawPosition(FALSE, boards[currentMove]);
6485             return;
6486         } else if (toX >= 0 && toY >= 0) {
6487             boards[0][toY][toX] = boards[0][fromY][fromX];
6488             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6489                 if(boards[0][fromY][0] != EmptySquare) {
6490                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6491                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6492                 }
6493             } else
6494             if(fromX == BOARD_RGHT+1) {
6495                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6496                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6497                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6498                 }
6499             } else
6500             boards[0][fromY][fromX] = EmptySquare;
6501             DrawPosition(FALSE, boards[currentMove]);
6502             return;
6503         }
6504         return;
6505     }
6506
6507     if(toX < 0 || toY < 0) return;
6508     pdown = boards[currentMove][fromY][fromX];
6509     pup = boards[currentMove][toY][toX];
6510
6511     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6512     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6513          if( pup != EmptySquare ) return;
6514          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6515            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6516                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6517            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6518            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6519            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6520            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6521          fromY = DROP_RANK;
6522     }
6523
6524     /* [HGM] always test for legality, to get promotion info */
6525     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6526                                          fromY, fromX, toY, toX, promoChar);
6527
6528     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6529
6530     /* [HGM] but possibly ignore an IllegalMove result */
6531     if (appData.testLegality) {
6532         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6533             DisplayMoveError(_("Illegal move"));
6534             return;
6535         }
6536     }
6537
6538     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6539 }
6540
6541 /* Common tail of UserMoveEvent and DropMenuEvent */
6542 int
6543 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6544 {
6545     char *bookHit = 0;
6546
6547     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6548         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6549         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6550         if(WhiteOnMove(currentMove)) {
6551             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6552         } else {
6553             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6554         }
6555     }
6556
6557     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6558        move type in caller when we know the move is a legal promotion */
6559     if(moveType == NormalMove && promoChar)
6560         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6561
6562     /* [HGM] <popupFix> The following if has been moved here from
6563        UserMoveEvent(). Because it seemed to belong here (why not allow
6564        piece drops in training games?), and because it can only be
6565        performed after it is known to what we promote. */
6566     if (gameMode == Training) {
6567       /* compare the move played on the board to the next move in the
6568        * game. If they match, display the move and the opponent's response.
6569        * If they don't match, display an error message.
6570        */
6571       int saveAnimate;
6572       Board testBoard;
6573       CopyBoard(testBoard, boards[currentMove]);
6574       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6575
6576       if (CompareBoards(testBoard, boards[currentMove+1])) {
6577         ForwardInner(currentMove+1);
6578
6579         /* Autoplay the opponent's response.
6580          * if appData.animate was TRUE when Training mode was entered,
6581          * the response will be animated.
6582          */
6583         saveAnimate = appData.animate;
6584         appData.animate = animateTraining;
6585         ForwardInner(currentMove+1);
6586         appData.animate = saveAnimate;
6587
6588         /* check for the end of the game */
6589         if (currentMove >= forwardMostMove) {
6590           gameMode = PlayFromGameFile;
6591           ModeHighlight();
6592           SetTrainingModeOff();
6593           DisplayInformation(_("End of game"));
6594         }
6595       } else {
6596         DisplayError(_("Incorrect move"), 0);
6597       }
6598       return 1;
6599     }
6600
6601   /* Ok, now we know that the move is good, so we can kill
6602      the previous line in Analysis Mode */
6603   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6604                                 && currentMove < forwardMostMove) {
6605     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6606     else forwardMostMove = currentMove;
6607   }
6608
6609   /* If we need the chess program but it's dead, restart it */
6610   ResurrectChessProgram();
6611
6612   /* A user move restarts a paused game*/
6613   if (pausing)
6614     PauseEvent();
6615
6616   thinkOutput[0] = NULLCHAR;
6617
6618   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6619
6620   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6621     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6622     return 1;
6623   }
6624
6625   if (gameMode == BeginningOfGame) {
6626     if (appData.noChessProgram) {
6627       gameMode = EditGame;
6628       SetGameInfo();
6629     } else {
6630       char buf[MSG_SIZ];
6631       gameMode = MachinePlaysBlack;
6632       StartClocks();
6633       SetGameInfo();
6634       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6635       DisplayTitle(buf);
6636       if (first.sendName) {
6637         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6638         SendToProgram(buf, &first);
6639       }
6640       StartClocks();
6641     }
6642     ModeHighlight();
6643   }
6644
6645   /* Relay move to ICS or chess engine */
6646   if (appData.icsActive) {
6647     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6648         gameMode == IcsExamining) {
6649       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6650         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6651         SendToICS("draw ");
6652         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6653       }
6654       // also send plain move, in case ICS does not understand atomic claims
6655       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6656       ics_user_moved = 1;
6657     }
6658   } else {
6659     if (first.sendTime && (gameMode == BeginningOfGame ||
6660                            gameMode == MachinePlaysWhite ||
6661                            gameMode == MachinePlaysBlack)) {
6662       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6663     }
6664     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6665          // [HGM] book: if program might be playing, let it use book
6666         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6667         first.maybeThinking = TRUE;
6668     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6669         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6670         SendBoard(&first, currentMove+1);
6671     } else SendMoveToProgram(forwardMostMove-1, &first);
6672     if (currentMove == cmailOldMove + 1) {
6673       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6674     }
6675   }
6676
6677   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6678
6679   switch (gameMode) {
6680   case EditGame:
6681     if(appData.testLegality)
6682     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6683     case MT_NONE:
6684     case MT_CHECK:
6685       break;
6686     case MT_CHECKMATE:
6687     case MT_STAINMATE:
6688       if (WhiteOnMove(currentMove)) {
6689         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6690       } else {
6691         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6692       }
6693       break;
6694     case MT_STALEMATE:
6695       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6696       break;
6697     }
6698     break;
6699
6700   case MachinePlaysBlack:
6701   case MachinePlaysWhite:
6702     /* disable certain menu options while machine is thinking */
6703     SetMachineThinkingEnables();
6704     break;
6705
6706   default:
6707     break;
6708   }
6709
6710   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6711   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6712
6713   if(bookHit) { // [HGM] book: simulate book reply
6714         static char bookMove[MSG_SIZ]; // a bit generous?
6715
6716         programStats.nodes = programStats.depth = programStats.time =
6717         programStats.score = programStats.got_only_move = 0;
6718         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6719
6720         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6721         strcat(bookMove, bookHit);
6722         HandleMachineMove(bookMove, &first);
6723   }
6724   return 1;
6725 }
6726
6727 void
6728 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6729 {
6730     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6731     Markers *m = (Markers *) closure;
6732     if(rf == fromY && ff == fromX)
6733         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6734                          || kind == WhiteCapturesEnPassant
6735                          || kind == BlackCapturesEnPassant);
6736     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6737 }
6738
6739 void
6740 MarkTargetSquares (int clear)
6741 {
6742   int x, y;
6743   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6744      !appData.testLegality || gameMode == EditPosition) return;
6745   if(clear) {
6746     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6747   } else {
6748     int capt = 0;
6749     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6750     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6751       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6752       if(capt)
6753       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6754     }
6755   }
6756   DrawPosition(TRUE, NULL);
6757 }
6758
6759 int
6760 Explode (Board board, int fromX, int fromY, int toX, int toY)
6761 {
6762     if(gameInfo.variant == VariantAtomic &&
6763        (board[toY][toX] != EmptySquare ||                     // capture?
6764         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6765                          board[fromY][fromX] == BlackPawn   )
6766       )) {
6767         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6768         return TRUE;
6769     }
6770     return FALSE;
6771 }
6772
6773 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6774
6775 int
6776 CanPromote (ChessSquare piece, int y)
6777 {
6778         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6779         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6780         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6781            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6782            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6783                                                   gameInfo.variant == VariantMakruk) return FALSE;
6784         return (piece == BlackPawn && y == 1 ||
6785                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6786                 piece == BlackLance && y == 1 ||
6787                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6788 }
6789
6790 void
6791 LeftClick (ClickType clickType, int xPix, int yPix)
6792 {
6793     int x, y;
6794     Boolean saveAnimate;
6795     static int second = 0, promotionChoice = 0, clearFlag = 0;
6796     char promoChoice = NULLCHAR;
6797     ChessSquare piece;
6798
6799     if(appData.seekGraph && appData.icsActive && loggedOn &&
6800         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6801         SeekGraphClick(clickType, xPix, yPix, 0);
6802         return;
6803     }
6804
6805     if (clickType == Press) ErrorPopDown();
6806
6807     x = EventToSquare(xPix, BOARD_WIDTH);
6808     y = EventToSquare(yPix, BOARD_HEIGHT);
6809     if (!flipView && y >= 0) {
6810         y = BOARD_HEIGHT - 1 - y;
6811     }
6812     if (flipView && x >= 0) {
6813         x = BOARD_WIDTH - 1 - x;
6814     }
6815
6816     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6817         defaultPromoChoice = promoSweep;
6818         promoSweep = EmptySquare;   // terminate sweep
6819         promoDefaultAltered = TRUE;
6820         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6821     }
6822
6823     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6824         if(clickType == Release) return; // ignore upclick of click-click destination
6825         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6826         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6827         if(gameInfo.holdingsWidth &&
6828                 (WhiteOnMove(currentMove)
6829                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6830                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6831             // click in right holdings, for determining promotion piece
6832             ChessSquare p = boards[currentMove][y][x];
6833             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6834             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6835             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6836                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6837                 fromX = fromY = -1;
6838                 return;
6839             }
6840         }
6841         DrawPosition(FALSE, boards[currentMove]);
6842         return;
6843     }
6844
6845     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6846     if(clickType == Press
6847             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6848               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6849               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6850         return;
6851
6852     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6853         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6854
6855     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6856         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6857                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6858         defaultPromoChoice = DefaultPromoChoice(side);
6859     }
6860
6861     autoQueen = appData.alwaysPromoteToQueen;
6862
6863     if (fromX == -1) {
6864       int originalY = y;
6865       gatingPiece = EmptySquare;
6866       if (clickType != Press) {
6867         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6868             DragPieceEnd(xPix, yPix); dragging = 0;
6869             DrawPosition(FALSE, NULL);
6870         }
6871         return;
6872       }
6873       fromX = x; fromY = y; toX = toY = -1;
6874       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6875          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6876          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6877             /* First square */
6878             if (OKToStartUserMove(fromX, fromY)) {
6879                 second = 0;
6880                 MarkTargetSquares(0);
6881                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6882                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6883                     promoSweep = defaultPromoChoice;
6884                     selectFlag = 0; lastX = xPix; lastY = yPix;
6885                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6886                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6887                 }
6888                 if (appData.highlightDragging) {
6889                     SetHighlights(fromX, fromY, -1, -1);
6890                 }
6891             } else fromX = fromY = -1;
6892             return;
6893         }
6894     }
6895
6896     /* fromX != -1 */
6897     if (clickType == Press && gameMode != EditPosition) {
6898         ChessSquare fromP;
6899         ChessSquare toP;
6900         int frc;
6901
6902         // ignore off-board to clicks
6903         if(y < 0 || x < 0) return;
6904
6905         /* Check if clicking again on the same color piece */
6906         fromP = boards[currentMove][fromY][fromX];
6907         toP = boards[currentMove][y][x];
6908         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6909         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6910              WhitePawn <= toP && toP <= WhiteKing &&
6911              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6912              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6913             (BlackPawn <= fromP && fromP <= BlackKing &&
6914              BlackPawn <= toP && toP <= BlackKing &&
6915              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6916              !(fromP == BlackKing && toP == BlackRook && frc))) {
6917             /* Clicked again on same color piece -- changed his mind */
6918             second = (x == fromX && y == fromY);
6919             promoDefaultAltered = FALSE;
6920             MarkTargetSquares(1);
6921            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6922             if (appData.highlightDragging) {
6923                 SetHighlights(x, y, -1, -1);
6924             } else {
6925                 ClearHighlights();
6926             }
6927             if (OKToStartUserMove(x, y)) {
6928                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6929                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6930                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6931                  gatingPiece = boards[currentMove][fromY][fromX];
6932                 else gatingPiece = EmptySquare;
6933                 fromX = x;
6934                 fromY = y; dragging = 1;
6935                 MarkTargetSquares(0);
6936                 DragPieceBegin(xPix, yPix, FALSE);
6937                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6938                     promoSweep = defaultPromoChoice;
6939                     selectFlag = 0; lastX = xPix; lastY = yPix;
6940                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6941                 }
6942             }
6943            }
6944            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6945            second = FALSE; 
6946         }
6947         // ignore clicks on holdings
6948         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6949     }
6950
6951     if (clickType == Release && x == fromX && y == fromY) {
6952         DragPieceEnd(xPix, yPix); dragging = 0;
6953         if(clearFlag) {
6954             // a deferred attempt to click-click move an empty square on top of a piece
6955             boards[currentMove][y][x] = EmptySquare;
6956             ClearHighlights();
6957             DrawPosition(FALSE, boards[currentMove]);
6958             fromX = fromY = -1; clearFlag = 0;
6959             return;
6960         }
6961         if (appData.animateDragging) {
6962             /* Undo animation damage if any */
6963             DrawPosition(FALSE, NULL);
6964         }
6965         if (second) {
6966             /* Second up/down in same square; just abort move */
6967             second = 0;
6968             fromX = fromY = -1;
6969             gatingPiece = EmptySquare;
6970             ClearHighlights();
6971             gotPremove = 0;
6972             ClearPremoveHighlights();
6973         } else {
6974             /* First upclick in same square; start click-click mode */
6975             SetHighlights(x, y, -1, -1);
6976         }
6977         return;
6978     }
6979
6980     clearFlag = 0;
6981
6982     /* we now have a different from- and (possibly off-board) to-square */
6983     /* Completed move */
6984     toX = x;
6985     toY = y;
6986     saveAnimate = appData.animate;
6987     MarkTargetSquares(1);
6988     if (clickType == Press) {
6989         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6990             // must be Edit Position mode with empty-square selected
6991             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
6992             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6993             return;
6994         }
6995         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
6996             ChessSquare piece = boards[currentMove][fromY][fromX];
6997             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
6998             promoSweep = defaultPromoChoice;
6999             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7000             selectFlag = 0; lastX = xPix; lastY = yPix;
7001             Sweep(0); // Pawn that is going to promote: preview promotion piece
7002             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7003             DrawPosition(FALSE, boards[currentMove]);
7004             return;
7005         }
7006         /* Finish clickclick move */
7007         if (appData.animate || appData.highlightLastMove) {
7008             SetHighlights(fromX, fromY, toX, toY);
7009         } else {
7010             ClearHighlights();
7011         }
7012     } else {
7013         /* Finish drag move */
7014         if (appData.highlightLastMove) {
7015             SetHighlights(fromX, fromY, toX, toY);
7016         } else {
7017             ClearHighlights();
7018         }
7019         DragPieceEnd(xPix, yPix); dragging = 0;
7020         /* Don't animate move and drag both */
7021         appData.animate = FALSE;
7022     }
7023
7024     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7025     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7026         ChessSquare piece = boards[currentMove][fromY][fromX];
7027         if(gameMode == EditPosition && piece != EmptySquare &&
7028            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7029             int n;
7030
7031             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7032                 n = PieceToNumber(piece - (int)BlackPawn);
7033                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7034                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7035                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7036             } else
7037             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7038                 n = PieceToNumber(piece);
7039                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7040                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7041                 boards[currentMove][n][BOARD_WIDTH-2]++;
7042             }
7043             boards[currentMove][fromY][fromX] = EmptySquare;
7044         }
7045         ClearHighlights();
7046         fromX = fromY = -1;
7047         DrawPosition(TRUE, boards[currentMove]);
7048         return;
7049     }
7050
7051     // off-board moves should not be highlighted
7052     if(x < 0 || y < 0) ClearHighlights();
7053
7054     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7055
7056     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7057         SetHighlights(fromX, fromY, toX, toY);
7058         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7059             // [HGM] super: promotion to captured piece selected from holdings
7060             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7061             promotionChoice = TRUE;
7062             // kludge follows to temporarily execute move on display, without promoting yet
7063             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7064             boards[currentMove][toY][toX] = p;
7065             DrawPosition(FALSE, boards[currentMove]);
7066             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7067             boards[currentMove][toY][toX] = q;
7068             DisplayMessage("Click in holdings to choose piece", "");
7069             return;
7070         }
7071         PromotionPopUp();
7072     } else {
7073         int oldMove = currentMove;
7074         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7075         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7076         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7077         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7078            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7079             DrawPosition(TRUE, boards[currentMove]);
7080         fromX = fromY = -1;
7081     }
7082     appData.animate = saveAnimate;
7083     if (appData.animate || appData.animateDragging) {
7084         /* Undo animation damage if needed */
7085         DrawPosition(FALSE, NULL);
7086     }
7087 }
7088
7089 int
7090 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7091 {   // front-end-free part taken out of PieceMenuPopup
7092     int whichMenu; int xSqr, ySqr;
7093
7094     if(seekGraphUp) { // [HGM] seekgraph
7095         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7096         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7097         return -2;
7098     }
7099
7100     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7101          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7102         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7103         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7104         if(action == Press)   {
7105             originalFlip = flipView;
7106             flipView = !flipView; // temporarily flip board to see game from partners perspective
7107             DrawPosition(TRUE, partnerBoard);
7108             DisplayMessage(partnerStatus, "");
7109             partnerUp = TRUE;
7110         } else if(action == Release) {
7111             flipView = originalFlip;
7112             DrawPosition(TRUE, boards[currentMove]);
7113             partnerUp = FALSE;
7114         }
7115         return -2;
7116     }
7117
7118     xSqr = EventToSquare(x, BOARD_WIDTH);
7119     ySqr = EventToSquare(y, BOARD_HEIGHT);
7120     if (action == Release) {
7121         if(pieceSweep != EmptySquare) {
7122             EditPositionMenuEvent(pieceSweep, toX, toY);
7123             pieceSweep = EmptySquare;
7124         } else UnLoadPV(); // [HGM] pv
7125     }
7126     if (action != Press) return -2; // return code to be ignored
7127     switch (gameMode) {
7128       case IcsExamining:
7129         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7130       case EditPosition:
7131         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7132         if (xSqr < 0 || ySqr < 0) return -1;
7133         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7134         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7135         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7136         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7137         NextPiece(0);
7138         return 2; // grab
7139       case IcsObserving:
7140         if(!appData.icsEngineAnalyze) return -1;
7141       case IcsPlayingWhite:
7142       case IcsPlayingBlack:
7143         if(!appData.zippyPlay) goto noZip;
7144       case AnalyzeMode:
7145       case AnalyzeFile:
7146       case MachinePlaysWhite:
7147       case MachinePlaysBlack:
7148       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7149         if (!appData.dropMenu) {
7150           LoadPV(x, y);
7151           return 2; // flag front-end to grab mouse events
7152         }
7153         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7154            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7155       case EditGame:
7156       noZip:
7157         if (xSqr < 0 || ySqr < 0) return -1;
7158         if (!appData.dropMenu || appData.testLegality &&
7159             gameInfo.variant != VariantBughouse &&
7160             gameInfo.variant != VariantCrazyhouse) return -1;
7161         whichMenu = 1; // drop menu
7162         break;
7163       default:
7164         return -1;
7165     }
7166
7167     if (((*fromX = xSqr) < 0) ||
7168         ((*fromY = ySqr) < 0)) {
7169         *fromX = *fromY = -1;
7170         return -1;
7171     }
7172     if (flipView)
7173       *fromX = BOARD_WIDTH - 1 - *fromX;
7174     else
7175       *fromY = BOARD_HEIGHT - 1 - *fromY;
7176
7177     return whichMenu;
7178 }
7179
7180 void
7181 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7182 {
7183 //    char * hint = lastHint;
7184     FrontEndProgramStats stats;
7185
7186     stats.which = cps == &first ? 0 : 1;
7187     stats.depth = cpstats->depth;
7188     stats.nodes = cpstats->nodes;
7189     stats.score = cpstats->score;
7190     stats.time = cpstats->time;
7191     stats.pv = cpstats->movelist;
7192     stats.hint = lastHint;
7193     stats.an_move_index = 0;
7194     stats.an_move_count = 0;
7195
7196     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7197         stats.hint = cpstats->move_name;
7198         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7199         stats.an_move_count = cpstats->nr_moves;
7200     }
7201
7202     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
7203
7204     SetProgramStats( &stats );
7205 }
7206
7207 void
7208 ClearEngineOutputPane (int which)
7209 {
7210     static FrontEndProgramStats dummyStats;
7211     dummyStats.which = which;
7212     dummyStats.pv = "#";
7213     SetProgramStats( &dummyStats );
7214 }
7215
7216 #define MAXPLAYERS 500
7217
7218 char *
7219 TourneyStandings (int display)
7220 {
7221     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7222     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7223     char result, *p, *names[MAXPLAYERS];
7224
7225     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7226         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7227     names[0] = p = strdup(appData.participants);
7228     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7229
7230     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7231
7232     while(result = appData.results[nr]) {
7233         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7234         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7235         wScore = bScore = 0;
7236         switch(result) {
7237           case '+': wScore = 2; break;
7238           case '-': bScore = 2; break;
7239           case '=': wScore = bScore = 1; break;
7240           case ' ':
7241           case '*': return strdup("busy"); // tourney not finished
7242         }
7243         score[w] += wScore;
7244         score[b] += bScore;
7245         games[w]++;
7246         games[b]++;
7247         nr++;
7248     }
7249     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7250     for(w=0; w<nPlayers; w++) {
7251         bScore = -1;
7252         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7253         ranking[w] = b; points[w] = bScore; score[b] = -2;
7254     }
7255     p = malloc(nPlayers*34+1);
7256     for(w=0; w<nPlayers && w<display; w++)
7257         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7258     free(names[0]);
7259     return p;
7260 }
7261
7262 void
7263 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7264 {       // count all piece types
7265         int p, f, r;
7266         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7267         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7268         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7269                 p = board[r][f];
7270                 pCnt[p]++;
7271                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7272                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7273                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7274                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7275                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7276                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7277         }
7278 }
7279
7280 int
7281 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7282 {
7283         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7284         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7285
7286         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7287         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7288         if(myPawns == 2 && nMine == 3) // KPP
7289             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7290         if(myPawns == 1 && nMine == 2) // KP
7291             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7292         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7293             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7294         if(myPawns) return FALSE;
7295         if(pCnt[WhiteRook+side])
7296             return pCnt[BlackRook-side] ||
7297                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7298                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7299                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7300         if(pCnt[WhiteCannon+side]) {
7301             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7302             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7303         }
7304         if(pCnt[WhiteKnight+side])
7305             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7306         return FALSE;
7307 }
7308
7309 int
7310 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7311 {
7312         VariantClass v = gameInfo.variant;
7313
7314         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7315         if(v == VariantShatranj) return TRUE; // always winnable through baring
7316         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7317         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7318
7319         if(v == VariantXiangqi) {
7320                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7321
7322                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7323                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7324                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7325                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7326                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7327                 if(stale) // we have at least one last-rank P plus perhaps C
7328                     return majors // KPKX
7329                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7330                 else // KCA*E*
7331                     return pCnt[WhiteFerz+side] // KCAK
7332                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7333                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7334                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7335
7336         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7337                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7338
7339                 if(nMine == 1) return FALSE; // bare King
7340                 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
7341                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7342                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7343                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7344                 if(pCnt[WhiteKnight+side])
7345                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7346                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7347                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7348                 if(nBishops)
7349                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7350                 if(pCnt[WhiteAlfil+side])
7351                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7352                 if(pCnt[WhiteWazir+side])
7353                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7354         }
7355
7356         return TRUE;
7357 }
7358
7359 int
7360 CompareWithRights (Board b1, Board b2)
7361 {
7362     int rights = 0;
7363     if(!CompareBoards(b1, b2)) return FALSE;
7364     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7365     /* compare castling rights */
7366     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7367            rights++; /* King lost rights, while rook still had them */
7368     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7369         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7370            rights++; /* but at least one rook lost them */
7371     }
7372     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7373            rights++;
7374     if( b1[CASTLING][5] != NoRights ) {
7375         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7376            rights++;
7377     }
7378     return rights == 0;
7379 }
7380
7381 int
7382 Adjudicate (ChessProgramState *cps)
7383 {       // [HGM] some adjudications useful with buggy engines
7384         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7385         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7386         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7387         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7388         int k, count = 0; static int bare = 1;
7389         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7390         Boolean canAdjudicate = !appData.icsActive;
7391
7392         // most tests only when we understand the game, i.e. legality-checking on
7393             if( appData.testLegality )
7394             {   /* [HGM] Some more adjudications for obstinate engines */
7395                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7396                 static int moveCount = 6;
7397                 ChessMove result;
7398                 char *reason = NULL;
7399
7400                 /* Count what is on board. */
7401                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7402
7403                 /* Some material-based adjudications that have to be made before stalemate test */
7404                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7405                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7406                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7407                      if(canAdjudicate && appData.checkMates) {
7408                          if(engineOpponent)
7409                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7410                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7411                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7412                          return 1;
7413                      }
7414                 }
7415
7416                 /* Bare King in Shatranj (loses) or Losers (wins) */
7417                 if( nrW == 1 || nrB == 1) {
7418                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7419                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7420                      if(canAdjudicate && appData.checkMates) {
7421                          if(engineOpponent)
7422                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7423                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7424                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7425                          return 1;
7426                      }
7427                   } else
7428                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7429                   {    /* bare King */
7430                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7431                         if(canAdjudicate && appData.checkMates) {
7432                             /* but only adjudicate if adjudication enabled */
7433                             if(engineOpponent)
7434                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7435                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7436                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7437                             return 1;
7438                         }
7439                   }
7440                 } else bare = 1;
7441
7442
7443             // don't wait for engine to announce game end if we can judge ourselves
7444             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7445               case MT_CHECK:
7446                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7447                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7448                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7449                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7450                             checkCnt++;
7451                         if(checkCnt >= 2) {
7452                             reason = "Xboard adjudication: 3rd check";
7453                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7454                             break;
7455                         }
7456                     }
7457                 }
7458               case MT_NONE:
7459               default:
7460                 break;
7461               case MT_STALEMATE:
7462               case MT_STAINMATE:
7463                 reason = "Xboard adjudication: Stalemate";
7464                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7465                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7466                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7467                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7468                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7469                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7470                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7471                                                                         EP_CHECKMATE : EP_WINS);
7472                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7473                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7474                 }
7475                 break;
7476               case MT_CHECKMATE:
7477                 reason = "Xboard adjudication: Checkmate";
7478                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7479                 break;
7480             }
7481
7482                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7483                     case EP_STALEMATE:
7484                         result = GameIsDrawn; break;
7485                     case EP_CHECKMATE:
7486                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7487                     case EP_WINS:
7488                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7489                     default:
7490                         result = EndOfFile;
7491                 }
7492                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7493                     if(engineOpponent)
7494                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7495                     GameEnds( result, reason, GE_XBOARD );
7496                     return 1;
7497                 }
7498
7499                 /* Next absolutely insufficient mating material. */
7500                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7501                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7502                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7503
7504                      /* always flag draws, for judging claims */
7505                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7506
7507                      if(canAdjudicate && appData.materialDraws) {
7508                          /* but only adjudicate them if adjudication enabled */
7509                          if(engineOpponent) {
7510                            SendToProgram("force\n", engineOpponent); // suppress reply
7511                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7512                          }
7513                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7514                          return 1;
7515                      }
7516                 }
7517
7518                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7519                 if(gameInfo.variant == VariantXiangqi ?
7520                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7521                  : nrW + nrB == 4 &&
7522                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7523                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7524                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7525                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7526                    ) ) {
7527                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7528                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7529                           if(engineOpponent) {
7530                             SendToProgram("force\n", engineOpponent); // suppress reply
7531                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7532                           }
7533                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7534                           return 1;
7535                      }
7536                 } else moveCount = 6;
7537             }
7538         if (appData.debugMode) { int i;
7539             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7540                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7541                     appData.drawRepeats);
7542             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7543               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7544
7545         }
7546
7547         // Repetition draws and 50-move rule can be applied independently of legality testing
7548
7549                 /* Check for rep-draws */
7550                 count = 0;
7551                 for(k = forwardMostMove-2;
7552                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7553                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7554                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7555                     k-=2)
7556                 {   int rights=0;
7557                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7558                         /* compare castling rights */
7559                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7560                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7561                                 rights++; /* King lost rights, while rook still had them */
7562                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7563                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7564                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7565                                    rights++; /* but at least one rook lost them */
7566                         }
7567                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7568                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7569                                 rights++;
7570                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7571                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7572                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7573                                    rights++;
7574                         }
7575                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7576                             && appData.drawRepeats > 1) {
7577                              /* adjudicate after user-specified nr of repeats */
7578                              int result = GameIsDrawn;
7579                              char *details = "XBoard adjudication: repetition draw";
7580                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7581                                 // [HGM] xiangqi: check for forbidden perpetuals
7582                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7583                                 for(m=forwardMostMove; m>k; m-=2) {
7584                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7585                                         ourPerpetual = 0; // the current mover did not always check
7586                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7587                                         hisPerpetual = 0; // the opponent did not always check
7588                                 }
7589                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7590                                                                         ourPerpetual, hisPerpetual);
7591                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7592                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7593                                     details = "Xboard adjudication: perpetual checking";
7594                                 } else
7595                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7596                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7597                                 } else
7598                                 // Now check for perpetual chases
7599                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7600                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7601                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7602                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7603                                         static char resdet[MSG_SIZ];
7604                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7605                                         details = resdet;
7606                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7607                                     } else
7608                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7609                                         break; // Abort repetition-checking loop.
7610                                 }
7611                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7612                              }
7613                              if(engineOpponent) {
7614                                SendToProgram("force\n", engineOpponent); // suppress reply
7615                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7616                              }
7617                              GameEnds( result, details, GE_XBOARD );
7618                              return 1;
7619                         }
7620                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7621                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7622                     }
7623                 }
7624
7625                 /* Now we test for 50-move draws. Determine ply count */
7626                 count = forwardMostMove;
7627                 /* look for last irreversble move */
7628                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7629                     count--;
7630                 /* if we hit starting position, add initial plies */
7631                 if( count == backwardMostMove )
7632                     count -= initialRulePlies;
7633                 count = forwardMostMove - count;
7634                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7635                         // adjust reversible move counter for checks in Xiangqi
7636                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7637                         if(i < backwardMostMove) i = backwardMostMove;
7638                         while(i <= forwardMostMove) {
7639                                 lastCheck = inCheck; // check evasion does not count
7640                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7641                                 if(inCheck || lastCheck) count--; // check does not count
7642                                 i++;
7643                         }
7644                 }
7645                 if( count >= 100)
7646                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7647                          /* this is used to judge if draw claims are legal */
7648                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7649                          if(engineOpponent) {
7650                            SendToProgram("force\n", engineOpponent); // suppress reply
7651                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7652                          }
7653                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7654                          return 1;
7655                 }
7656
7657                 /* if draw offer is pending, treat it as a draw claim
7658                  * when draw condition present, to allow engines a way to
7659                  * claim draws before making their move to avoid a race
7660                  * condition occurring after their move
7661                  */
7662                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7663                          char *p = NULL;
7664                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7665                              p = "Draw claim: 50-move rule";
7666                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7667                              p = "Draw claim: 3-fold repetition";
7668                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7669                              p = "Draw claim: insufficient mating material";
7670                          if( p != NULL && canAdjudicate) {
7671                              if(engineOpponent) {
7672                                SendToProgram("force\n", engineOpponent); // suppress reply
7673                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7674                              }
7675                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7676                              return 1;
7677                          }
7678                 }
7679
7680                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7681                     if(engineOpponent) {
7682                       SendToProgram("force\n", engineOpponent); // suppress reply
7683                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7684                     }
7685                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7686                     return 1;
7687                 }
7688         return 0;
7689 }
7690
7691 char *
7692 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7693 {   // [HGM] book: this routine intercepts moves to simulate book replies
7694     char *bookHit = NULL;
7695
7696     //first determine if the incoming move brings opponent into his book
7697     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7698         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7699     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7700     if(bookHit != NULL && !cps->bookSuspend) {
7701         // make sure opponent is not going to reply after receiving move to book position
7702         SendToProgram("force\n", cps);
7703         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7704     }
7705     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7706     // now arrange restart after book miss
7707     if(bookHit) {
7708         // after a book hit we never send 'go', and the code after the call to this routine
7709         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7710         char buf[MSG_SIZ], *move = bookHit;
7711         if(cps->useSAN) {
7712             int fromX, fromY, toX, toY;
7713             char promoChar;
7714             ChessMove moveType;
7715             move = buf + 30;
7716             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7717                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7718                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7719                                     PosFlags(forwardMostMove),
7720                                     fromY, fromX, toY, toX, promoChar, move);
7721             } else {
7722                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7723                 bookHit = NULL;
7724             }
7725         }
7726         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7727         SendToProgram(buf, cps);
7728         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7729     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7730         SendToProgram("go\n", cps);
7731         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7732     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7733         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7734             SendToProgram("go\n", cps);
7735         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7736     }
7737     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7738 }
7739
7740 char *savedMessage;
7741 ChessProgramState *savedState;
7742 void
7743 DeferredBookMove (void)
7744 {
7745         if(savedState->lastPing != savedState->lastPong)
7746                     ScheduleDelayedEvent(DeferredBookMove, 10);
7747         else
7748         HandleMachineMove(savedMessage, savedState);
7749 }
7750
7751 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7752
7753 void
7754 HandleMachineMove (char *message, ChessProgramState *cps)
7755 {
7756     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7757     char realname[MSG_SIZ];
7758     int fromX, fromY, toX, toY;
7759     ChessMove moveType;
7760     char promoChar;
7761     char *p, *pv=buf1;
7762     int machineWhite;
7763     char *bookHit;
7764
7765     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7766         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7767         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7768             DisplayError(_("Invalid pairing from pairing engine"), 0);
7769             return;
7770         }
7771         pairingReceived = 1;
7772         NextMatchGame();
7773         return; // Skim the pairing messages here.
7774     }
7775
7776     cps->userError = 0;
7777
7778 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7779     /*
7780      * Kludge to ignore BEL characters
7781      */
7782     while (*message == '\007') message++;
7783
7784     /*
7785      * [HGM] engine debug message: ignore lines starting with '#' character
7786      */
7787     if(cps->debug && *message == '#') return;
7788
7789     /*
7790      * Look for book output
7791      */
7792     if (cps == &first && bookRequested) {
7793         if (message[0] == '\t' || message[0] == ' ') {
7794             /* Part of the book output is here; append it */
7795             strcat(bookOutput, message);
7796             strcat(bookOutput, "  \n");
7797             return;
7798         } else if (bookOutput[0] != NULLCHAR) {
7799             /* All of book output has arrived; display it */
7800             char *p = bookOutput;
7801             while (*p != NULLCHAR) {
7802                 if (*p == '\t') *p = ' ';
7803                 p++;
7804             }
7805             DisplayInformation(bookOutput);
7806             bookRequested = FALSE;
7807             /* Fall through to parse the current output */
7808         }
7809     }
7810
7811     /*
7812      * Look for machine move.
7813      */
7814     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7815         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7816     {
7817         /* This method is only useful on engines that support ping */
7818         if (cps->lastPing != cps->lastPong) {
7819           if (gameMode == BeginningOfGame) {
7820             /* Extra move from before last new; ignore */
7821             if (appData.debugMode) {
7822                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7823             }
7824           } else {
7825             if (appData.debugMode) {
7826                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7827                         cps->which, gameMode);
7828             }
7829
7830             SendToProgram("undo\n", cps);
7831           }
7832           return;
7833         }
7834
7835         switch (gameMode) {
7836           case BeginningOfGame:
7837             /* Extra move from before last reset; ignore */
7838             if (appData.debugMode) {
7839                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7840             }
7841             return;
7842
7843           case EndOfGame:
7844           case IcsIdle:
7845           default:
7846             /* Extra move after we tried to stop.  The mode test is
7847                not a reliable way of detecting this problem, but it's
7848                the best we can do on engines that don't support ping.
7849             */
7850             if (appData.debugMode) {
7851                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7852                         cps->which, gameMode);
7853             }
7854             SendToProgram("undo\n", cps);
7855             return;
7856
7857           case MachinePlaysWhite:
7858           case IcsPlayingWhite:
7859             machineWhite = TRUE;
7860             break;
7861
7862           case MachinePlaysBlack:
7863           case IcsPlayingBlack:
7864             machineWhite = FALSE;
7865             break;
7866
7867           case TwoMachinesPlay:
7868             machineWhite = (cps->twoMachinesColor[0] == 'w');
7869             break;
7870         }
7871         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7872             if (appData.debugMode) {
7873                 fprintf(debugFP,
7874                         "Ignoring move out of turn by %s, gameMode %d"
7875                         ", forwardMost %d\n",
7876                         cps->which, gameMode, forwardMostMove);
7877             }
7878             return;
7879         }
7880
7881     if (appData.debugMode) { int f = forwardMostMove;
7882         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7883                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7884                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7885     }
7886         if(cps->alphaRank) AlphaRank(machineMove, 4);
7887         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7888                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7889             /* Machine move could not be parsed; ignore it. */
7890           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7891                     machineMove, _(cps->which));
7892             DisplayError(buf1, 0);
7893             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7894                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7895             if (gameMode == TwoMachinesPlay) {
7896               GameEnds(machineWhite ? BlackWins : WhiteWins,
7897                        buf1, GE_XBOARD);
7898             }
7899             return;
7900         }
7901
7902         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7903         /* So we have to redo legality test with true e.p. status here,  */
7904         /* to make sure an illegal e.p. capture does not slip through,   */
7905         /* to cause a forfeit on a justified illegal-move complaint      */
7906         /* of the opponent.                                              */
7907         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7908            ChessMove moveType;
7909            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7910                              fromY, fromX, toY, toX, promoChar);
7911             if (appData.debugMode) {
7912                 int i;
7913                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7914                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7915                 fprintf(debugFP, "castling rights\n");
7916             }
7917             if(moveType == IllegalMove) {
7918               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7919                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7920                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7921                            buf1, GE_XBOARD);
7922                 return;
7923            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7924            /* [HGM] Kludge to handle engines that send FRC-style castling
7925               when they shouldn't (like TSCP-Gothic) */
7926            switch(moveType) {
7927              case WhiteASideCastleFR:
7928              case BlackASideCastleFR:
7929                toX+=2;
7930                currentMoveString[2]++;
7931                break;
7932              case WhiteHSideCastleFR:
7933              case BlackHSideCastleFR:
7934                toX--;
7935                currentMoveString[2]--;
7936                break;
7937              default: ; // nothing to do, but suppresses warning of pedantic compilers
7938            }
7939         }
7940         hintRequested = FALSE;
7941         lastHint[0] = NULLCHAR;
7942         bookRequested = FALSE;
7943         /* Program may be pondering now */
7944         cps->maybeThinking = TRUE;
7945         if (cps->sendTime == 2) cps->sendTime = 1;
7946         if (cps->offeredDraw) cps->offeredDraw--;
7947
7948         /* [AS] Save move info*/
7949         pvInfoList[ forwardMostMove ].score = programStats.score;
7950         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7951         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7952
7953         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7954
7955         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7956         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7957             int count = 0;
7958
7959             while( count < adjudicateLossPlies ) {
7960                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7961
7962                 if( count & 1 ) {
7963                     score = -score; /* Flip score for winning side */
7964                 }
7965
7966                 if( score > adjudicateLossThreshold ) {
7967                     break;
7968                 }
7969
7970                 count++;
7971             }
7972
7973             if( count >= adjudicateLossPlies ) {
7974                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7975
7976                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7977                     "Xboard adjudication",
7978                     GE_XBOARD );
7979
7980                 return;
7981             }
7982         }
7983
7984         if(Adjudicate(cps)) {
7985             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7986             return; // [HGM] adjudicate: for all automatic game ends
7987         }
7988
7989 #if ZIPPY
7990         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7991             first.initDone) {
7992           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7993                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7994                 SendToICS("draw ");
7995                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7996           }
7997           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7998           ics_user_moved = 1;
7999           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8000                 char buf[3*MSG_SIZ];
8001
8002                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8003                         programStats.score / 100.,
8004                         programStats.depth,
8005                         programStats.time / 100.,
8006                         (unsigned int)programStats.nodes,
8007                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8008                         programStats.movelist);
8009                 SendToICS(buf);
8010 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8011           }
8012         }
8013 #endif
8014
8015         /* [AS] Clear stats for next move */
8016         ClearProgramStats();
8017         thinkOutput[0] = NULLCHAR;
8018         hiddenThinkOutputState = 0;
8019
8020         bookHit = NULL;
8021         if (gameMode == TwoMachinesPlay) {
8022             /* [HGM] relaying draw offers moved to after reception of move */
8023             /* and interpreting offer as claim if it brings draw condition */
8024             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8025                 SendToProgram("draw\n", cps->other);
8026             }
8027             if (cps->other->sendTime) {
8028                 SendTimeRemaining(cps->other,
8029                                   cps->other->twoMachinesColor[0] == 'w');
8030             }
8031             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8032             if (firstMove && !bookHit) {
8033                 firstMove = FALSE;
8034                 if (cps->other->useColors) {
8035                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8036                 }
8037                 SendToProgram("go\n", cps->other);
8038             }
8039             cps->other->maybeThinking = TRUE;
8040         }
8041
8042         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8043
8044         if (!pausing && appData.ringBellAfterMoves) {
8045             RingBell();
8046         }
8047
8048         /*
8049          * Reenable menu items that were disabled while
8050          * machine was thinking
8051          */
8052         if (gameMode != TwoMachinesPlay)
8053             SetUserThinkingEnables();
8054
8055         // [HGM] book: after book hit opponent has received move and is now in force mode
8056         // force the book reply into it, and then fake that it outputted this move by jumping
8057         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8058         if(bookHit) {
8059                 static char bookMove[MSG_SIZ]; // a bit generous?
8060
8061                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8062                 strcat(bookMove, bookHit);
8063                 message = bookMove;
8064                 cps = cps->other;
8065                 programStats.nodes = programStats.depth = programStats.time =
8066                 programStats.score = programStats.got_only_move = 0;
8067                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8068
8069                 if(cps->lastPing != cps->lastPong) {
8070                     savedMessage = message; // args for deferred call
8071                     savedState = cps;
8072                     ScheduleDelayedEvent(DeferredBookMove, 10);
8073                     return;
8074                 }
8075                 goto FakeBookMove;
8076         }
8077
8078         return;
8079     }
8080
8081     /* Set special modes for chess engines.  Later something general
8082      *  could be added here; for now there is just one kludge feature,
8083      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8084      *  when "xboard" is given as an interactive command.
8085      */
8086     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8087         cps->useSigint = FALSE;
8088         cps->useSigterm = FALSE;
8089     }
8090     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8091       ParseFeatures(message+8, cps);
8092       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8093     }
8094
8095     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8096                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8097       int dummy, s=6; char buf[MSG_SIZ];
8098       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8099       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8100       if(startedFromSetupPosition) return;
8101       ParseFEN(boards[0], &dummy, message+s);
8102       DrawPosition(TRUE, boards[0]);
8103       startedFromSetupPosition = TRUE;
8104       return;
8105     }
8106     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8107      * want this, I was asked to put it in, and obliged.
8108      */
8109     if (!strncmp(message, "setboard ", 9)) {
8110         Board initial_position;
8111
8112         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8113
8114         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8115             DisplayError(_("Bad FEN received from engine"), 0);
8116             return ;
8117         } else {
8118            Reset(TRUE, FALSE);
8119            CopyBoard(boards[0], initial_position);
8120            initialRulePlies = FENrulePlies;
8121            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8122            else gameMode = MachinePlaysBlack;
8123            DrawPosition(FALSE, boards[currentMove]);
8124         }
8125         return;
8126     }
8127
8128     /*
8129      * Look for communication commands
8130      */
8131     if (!strncmp(message, "telluser ", 9)) {
8132         if(message[9] == '\\' && message[10] == '\\')
8133             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8134         PlayTellSound();
8135         DisplayNote(message + 9);
8136         return;
8137     }
8138     if (!strncmp(message, "tellusererror ", 14)) {
8139         cps->userError = 1;
8140         if(message[14] == '\\' && message[15] == '\\')
8141             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8142         PlayTellSound();
8143         DisplayError(message + 14, 0);
8144         return;
8145     }
8146     if (!strncmp(message, "tellopponent ", 13)) {
8147       if (appData.icsActive) {
8148         if (loggedOn) {
8149           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8150           SendToICS(buf1);
8151         }
8152       } else {
8153         DisplayNote(message + 13);
8154       }
8155       return;
8156     }
8157     if (!strncmp(message, "tellothers ", 11)) {
8158       if (appData.icsActive) {
8159         if (loggedOn) {
8160           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8161           SendToICS(buf1);
8162         }
8163       }
8164       return;
8165     }
8166     if (!strncmp(message, "tellall ", 8)) {
8167       if (appData.icsActive) {
8168         if (loggedOn) {
8169           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8170           SendToICS(buf1);
8171         }
8172       } else {
8173         DisplayNote(message + 8);
8174       }
8175       return;
8176     }
8177     if (strncmp(message, "warning", 7) == 0) {
8178         /* Undocumented feature, use tellusererror in new code */
8179         DisplayError(message, 0);
8180         return;
8181     }
8182     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8183         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8184         strcat(realname, " query");
8185         AskQuestion(realname, buf2, buf1, cps->pr);
8186         return;
8187     }
8188     /* Commands from the engine directly to ICS.  We don't allow these to be
8189      *  sent until we are logged on. Crafty kibitzes have been known to
8190      *  interfere with the login process.
8191      */
8192     if (loggedOn) {
8193         if (!strncmp(message, "tellics ", 8)) {
8194             SendToICS(message + 8);
8195             SendToICS("\n");
8196             return;
8197         }
8198         if (!strncmp(message, "tellicsnoalias ", 15)) {
8199             SendToICS(ics_prefix);
8200             SendToICS(message + 15);
8201             SendToICS("\n");
8202             return;
8203         }
8204         /* The following are for backward compatibility only */
8205         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8206             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8207             SendToICS(ics_prefix);
8208             SendToICS(message);
8209             SendToICS("\n");
8210             return;
8211         }
8212     }
8213     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8214         return;
8215     }
8216     /*
8217      * If the move is illegal, cancel it and redraw the board.
8218      * Also deal with other error cases.  Matching is rather loose
8219      * here to accommodate engines written before the spec.
8220      */
8221     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8222         strncmp(message, "Error", 5) == 0) {
8223         if (StrStr(message, "name") ||
8224             StrStr(message, "rating") || StrStr(message, "?") ||
8225             StrStr(message, "result") || StrStr(message, "board") ||
8226             StrStr(message, "bk") || StrStr(message, "computer") ||
8227             StrStr(message, "variant") || StrStr(message, "hint") ||
8228             StrStr(message, "random") || StrStr(message, "depth") ||
8229             StrStr(message, "accepted")) {
8230             return;
8231         }
8232         if (StrStr(message, "protover")) {
8233           /* Program is responding to input, so it's apparently done
8234              initializing, and this error message indicates it is
8235              protocol version 1.  So we don't need to wait any longer
8236              for it to initialize and send feature commands. */
8237           FeatureDone(cps, 1);
8238           cps->protocolVersion = 1;
8239           return;
8240         }
8241         cps->maybeThinking = FALSE;
8242
8243         if (StrStr(message, "draw")) {
8244             /* Program doesn't have "draw" command */
8245             cps->sendDrawOffers = 0;
8246             return;
8247         }
8248         if (cps->sendTime != 1 &&
8249             (StrStr(message, "time") || StrStr(message, "otim"))) {
8250           /* Program apparently doesn't have "time" or "otim" command */
8251           cps->sendTime = 0;
8252           return;
8253         }
8254         if (StrStr(message, "analyze")) {
8255             cps->analysisSupport = FALSE;
8256             cps->analyzing = FALSE;
8257 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8258             EditGameEvent(); // [HGM] try to preserve loaded game
8259             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8260             DisplayError(buf2, 0);
8261             return;
8262         }
8263         if (StrStr(message, "(no matching move)st")) {
8264           /* Special kludge for GNU Chess 4 only */
8265           cps->stKludge = TRUE;
8266           SendTimeControl(cps, movesPerSession, timeControl,
8267                           timeIncrement, appData.searchDepth,
8268                           searchTime);
8269           return;
8270         }
8271         if (StrStr(message, "(no matching move)sd")) {
8272           /* Special kludge for GNU Chess 4 only */
8273           cps->sdKludge = TRUE;
8274           SendTimeControl(cps, movesPerSession, timeControl,
8275                           timeIncrement, appData.searchDepth,
8276                           searchTime);
8277           return;
8278         }
8279         if (!StrStr(message, "llegal")) {
8280             return;
8281         }
8282         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8283             gameMode == IcsIdle) return;
8284         if (forwardMostMove <= backwardMostMove) return;
8285         if (pausing) PauseEvent();
8286       if(appData.forceIllegal) {
8287             // [HGM] illegal: machine refused move; force position after move into it
8288           SendToProgram("force\n", cps);
8289           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8290                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8291                 // when black is to move, while there might be nothing on a2 or black
8292                 // might already have the move. So send the board as if white has the move.
8293                 // But first we must change the stm of the engine, as it refused the last move
8294                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8295                 if(WhiteOnMove(forwardMostMove)) {
8296                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8297                     SendBoard(cps, forwardMostMove); // kludgeless board
8298                 } else {
8299                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8300                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8301                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8302                 }
8303           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8304             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8305                  gameMode == TwoMachinesPlay)
8306               SendToProgram("go\n", cps);
8307             return;
8308       } else
8309         if (gameMode == PlayFromGameFile) {
8310             /* Stop reading this game file */
8311             gameMode = EditGame;
8312             ModeHighlight();
8313         }
8314         /* [HGM] illegal-move claim should forfeit game when Xboard */
8315         /* only passes fully legal moves                            */
8316         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8317             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8318                                 "False illegal-move claim", GE_XBOARD );
8319             return; // do not take back move we tested as valid
8320         }
8321         currentMove = forwardMostMove-1;
8322         DisplayMove(currentMove-1); /* before DisplayMoveError */
8323         SwitchClocks(forwardMostMove-1); // [HGM] race
8324         DisplayBothClocks();
8325         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8326                 parseList[currentMove], _(cps->which));
8327         DisplayMoveError(buf1);
8328         DrawPosition(FALSE, boards[currentMove]);
8329
8330         SetUserThinkingEnables();
8331         return;
8332     }
8333     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8334         /* Program has a broken "time" command that
8335            outputs a string not ending in newline.
8336            Don't use it. */
8337         cps->sendTime = 0;
8338     }
8339
8340     /*
8341      * If chess program startup fails, exit with an error message.
8342      * Attempts to recover here are futile.
8343      */
8344     if ((StrStr(message, "unknown host") != NULL)
8345         || (StrStr(message, "No remote directory") != NULL)
8346         || (StrStr(message, "not found") != NULL)
8347         || (StrStr(message, "No such file") != NULL)
8348         || (StrStr(message, "can't alloc") != NULL)
8349         || (StrStr(message, "Permission denied") != NULL)) {
8350
8351         cps->maybeThinking = FALSE;
8352         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8353                 _(cps->which), cps->program, cps->host, message);
8354         RemoveInputSource(cps->isr);
8355         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8356             if(cps == &first) appData.noChessProgram = TRUE;
8357             DisplayError(buf1, 0);
8358         }
8359         return;
8360     }
8361
8362     /*
8363      * Look for hint output
8364      */
8365     if (sscanf(message, "Hint: %s", buf1) == 1) {
8366         if (cps == &first && hintRequested) {
8367             hintRequested = FALSE;
8368             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8369                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8370                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8371                                     PosFlags(forwardMostMove),
8372                                     fromY, fromX, toY, toX, promoChar, buf1);
8373                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8374                 DisplayInformation(buf2);
8375             } else {
8376                 /* Hint move could not be parsed!? */
8377               snprintf(buf2, sizeof(buf2),
8378                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8379                         buf1, _(cps->which));
8380                 DisplayError(buf2, 0);
8381             }
8382         } else {
8383           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8384         }
8385         return;
8386     }
8387
8388     /*
8389      * Ignore other messages if game is not in progress
8390      */
8391     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8392         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8393
8394     /*
8395      * look for win, lose, draw, or draw offer
8396      */
8397     if (strncmp(message, "1-0", 3) == 0) {
8398         char *p, *q, *r = "";
8399         p = strchr(message, '{');
8400         if (p) {
8401             q = strchr(p, '}');
8402             if (q) {
8403                 *q = NULLCHAR;
8404                 r = p + 1;
8405             }
8406         }
8407         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8408         return;
8409     } else if (strncmp(message, "0-1", 3) == 0) {
8410         char *p, *q, *r = "";
8411         p = strchr(message, '{');
8412         if (p) {
8413             q = strchr(p, '}');
8414             if (q) {
8415                 *q = NULLCHAR;
8416                 r = p + 1;
8417             }
8418         }
8419         /* Kludge for Arasan 4.1 bug */
8420         if (strcmp(r, "Black resigns") == 0) {
8421             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8422             return;
8423         }
8424         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8425         return;
8426     } else if (strncmp(message, "1/2", 3) == 0) {
8427         char *p, *q, *r = "";
8428         p = strchr(message, '{');
8429         if (p) {
8430             q = strchr(p, '}');
8431             if (q) {
8432                 *q = NULLCHAR;
8433                 r = p + 1;
8434             }
8435         }
8436
8437         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8438         return;
8439
8440     } else if (strncmp(message, "White resign", 12) == 0) {
8441         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8442         return;
8443     } else if (strncmp(message, "Black resign", 12) == 0) {
8444         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8445         return;
8446     } else if (strncmp(message, "White matches", 13) == 0 ||
8447                strncmp(message, "Black matches", 13) == 0   ) {
8448         /* [HGM] ignore GNUShogi noises */
8449         return;
8450     } else if (strncmp(message, "White", 5) == 0 &&
8451                message[5] != '(' &&
8452                StrStr(message, "Black") == NULL) {
8453         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8454         return;
8455     } else if (strncmp(message, "Black", 5) == 0 &&
8456                message[5] != '(') {
8457         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8458         return;
8459     } else if (strcmp(message, "resign") == 0 ||
8460                strcmp(message, "computer resigns") == 0) {
8461         switch (gameMode) {
8462           case MachinePlaysBlack:
8463           case IcsPlayingBlack:
8464             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8465             break;
8466           case MachinePlaysWhite:
8467           case IcsPlayingWhite:
8468             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8469             break;
8470           case TwoMachinesPlay:
8471             if (cps->twoMachinesColor[0] == 'w')
8472               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8473             else
8474               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8475             break;
8476           default:
8477             /* can't happen */
8478             break;
8479         }
8480         return;
8481     } else if (strncmp(message, "opponent mates", 14) == 0) {
8482         switch (gameMode) {
8483           case MachinePlaysBlack:
8484           case IcsPlayingBlack:
8485             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8486             break;
8487           case MachinePlaysWhite:
8488           case IcsPlayingWhite:
8489             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8490             break;
8491           case TwoMachinesPlay:
8492             if (cps->twoMachinesColor[0] == 'w')
8493               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8494             else
8495               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8496             break;
8497           default:
8498             /* can't happen */
8499             break;
8500         }
8501         return;
8502     } else if (strncmp(message, "computer mates", 14) == 0) {
8503         switch (gameMode) {
8504           case MachinePlaysBlack:
8505           case IcsPlayingBlack:
8506             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8507             break;
8508           case MachinePlaysWhite:
8509           case IcsPlayingWhite:
8510             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8511             break;
8512           case TwoMachinesPlay:
8513             if (cps->twoMachinesColor[0] == 'w')
8514               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8515             else
8516               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8517             break;
8518           default:
8519             /* can't happen */
8520             break;
8521         }
8522         return;
8523     } else if (strncmp(message, "checkmate", 9) == 0) {
8524         if (WhiteOnMove(forwardMostMove)) {
8525             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8526         } else {
8527             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8528         }
8529         return;
8530     } else if (strstr(message, "Draw") != NULL ||
8531                strstr(message, "game is a draw") != NULL) {
8532         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8533         return;
8534     } else if (strstr(message, "offer") != NULL &&
8535                strstr(message, "draw") != NULL) {
8536 #if ZIPPY
8537         if (appData.zippyPlay && first.initDone) {
8538             /* Relay offer to ICS */
8539             SendToICS(ics_prefix);
8540             SendToICS("draw\n");
8541         }
8542 #endif
8543         cps->offeredDraw = 2; /* valid until this engine moves twice */
8544         if (gameMode == TwoMachinesPlay) {
8545             if (cps->other->offeredDraw) {
8546                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8547             /* [HGM] in two-machine mode we delay relaying draw offer      */
8548             /* until after we also have move, to see if it is really claim */
8549             }
8550         } else if (gameMode == MachinePlaysWhite ||
8551                    gameMode == MachinePlaysBlack) {
8552           if (userOfferedDraw) {
8553             DisplayInformation(_("Machine accepts your draw offer"));
8554             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8555           } else {
8556             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8557           }
8558         }
8559     }
8560
8561
8562     /*
8563      * Look for thinking output
8564      */
8565     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8566           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8567                                 ) {
8568         int plylev, mvleft, mvtot, curscore, time;
8569         char mvname[MOVE_LEN];
8570         u64 nodes; // [DM]
8571         char plyext;
8572         int ignore = FALSE;
8573         int prefixHint = FALSE;
8574         mvname[0] = NULLCHAR;
8575
8576         switch (gameMode) {
8577           case MachinePlaysBlack:
8578           case IcsPlayingBlack:
8579             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8580             break;
8581           case MachinePlaysWhite:
8582           case IcsPlayingWhite:
8583             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8584             break;
8585           case AnalyzeMode:
8586           case AnalyzeFile:
8587             break;
8588           case IcsObserving: /* [DM] icsEngineAnalyze */
8589             if (!appData.icsEngineAnalyze) ignore = TRUE;
8590             break;
8591           case TwoMachinesPlay:
8592             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8593                 ignore = TRUE;
8594             }
8595             break;
8596           default:
8597             ignore = TRUE;
8598             break;
8599         }
8600
8601         if (!ignore) {
8602             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8603             buf1[0] = NULLCHAR;
8604             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8605                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8606
8607                 if (plyext != ' ' && plyext != '\t') {
8608                     time *= 100;
8609                 }
8610
8611                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8612                 if( cps->scoreIsAbsolute &&
8613                     ( gameMode == MachinePlaysBlack ||
8614                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8615                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8616                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8617                      !WhiteOnMove(currentMove)
8618                     ) )
8619                 {
8620                     curscore = -curscore;
8621                 }
8622
8623                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8624
8625                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8626                         char buf[MSG_SIZ];
8627                         FILE *f;
8628                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8629                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8630                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8631                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8632                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8633                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8634                                 fclose(f);
8635                         } else DisplayError(_("failed writing PV"), 0);
8636                 }
8637
8638                 tempStats.depth = plylev;
8639                 tempStats.nodes = nodes;
8640                 tempStats.time = time;
8641                 tempStats.score = curscore;
8642                 tempStats.got_only_move = 0;
8643
8644                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8645                         int ticklen;
8646
8647                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8648                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8649                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8650                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8651                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8652                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8653                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8654                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8655                 }
8656
8657                 /* Buffer overflow protection */
8658                 if (pv[0] != NULLCHAR) {
8659                     if (strlen(pv) >= sizeof(tempStats.movelist)
8660                         && appData.debugMode) {
8661                         fprintf(debugFP,
8662                                 "PV is too long; using the first %u bytes.\n",
8663                                 (unsigned) sizeof(tempStats.movelist) - 1);
8664                     }
8665
8666                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8667                 } else {
8668                     sprintf(tempStats.movelist, " no PV\n");
8669                 }
8670
8671                 if (tempStats.seen_stat) {
8672                     tempStats.ok_to_send = 1;
8673                 }
8674
8675                 if (strchr(tempStats.movelist, '(') != NULL) {
8676                     tempStats.line_is_book = 1;
8677                     tempStats.nr_moves = 0;
8678                     tempStats.moves_left = 0;
8679                 } else {
8680                     tempStats.line_is_book = 0;
8681                 }
8682
8683                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8684                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8685
8686                 SendProgramStatsToFrontend( cps, &tempStats );
8687
8688                 /*
8689                     [AS] Protect the thinkOutput buffer from overflow... this
8690                     is only useful if buf1 hasn't overflowed first!
8691                 */
8692                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8693                          plylev,
8694                          (gameMode == TwoMachinesPlay ?
8695                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8696                          ((double) curscore) / 100.0,
8697                          prefixHint ? lastHint : "",
8698                          prefixHint ? " " : "" );
8699
8700                 if( buf1[0] != NULLCHAR ) {
8701                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8702
8703                     if( strlen(pv) > max_len ) {
8704                         if( appData.debugMode) {
8705                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8706                         }
8707                         pv[max_len+1] = '\0';
8708                     }
8709
8710                     strcat( thinkOutput, pv);
8711                 }
8712
8713                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8714                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8715                     DisplayMove(currentMove - 1);
8716                 }
8717                 return;
8718
8719             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8720                 /* crafty (9.25+) says "(only move) <move>"
8721                  * if there is only 1 legal move
8722                  */
8723                 sscanf(p, "(only move) %s", buf1);
8724                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8725                 sprintf(programStats.movelist, "%s (only move)", buf1);
8726                 programStats.depth = 1;
8727                 programStats.nr_moves = 1;
8728                 programStats.moves_left = 1;
8729                 programStats.nodes = 1;
8730                 programStats.time = 1;
8731                 programStats.got_only_move = 1;
8732
8733                 /* Not really, but we also use this member to
8734                    mean "line isn't going to change" (Crafty
8735                    isn't searching, so stats won't change) */
8736                 programStats.line_is_book = 1;
8737
8738                 SendProgramStatsToFrontend( cps, &programStats );
8739
8740                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8741                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8742                     DisplayMove(currentMove - 1);
8743                 }
8744                 return;
8745             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8746                               &time, &nodes, &plylev, &mvleft,
8747                               &mvtot, mvname) >= 5) {
8748                 /* The stat01: line is from Crafty (9.29+) in response
8749                    to the "." command */
8750                 programStats.seen_stat = 1;
8751                 cps->maybeThinking = TRUE;
8752
8753                 if (programStats.got_only_move || !appData.periodicUpdates)
8754                   return;
8755
8756                 programStats.depth = plylev;
8757                 programStats.time = time;
8758                 programStats.nodes = nodes;
8759                 programStats.moves_left = mvleft;
8760                 programStats.nr_moves = mvtot;
8761                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8762                 programStats.ok_to_send = 1;
8763                 programStats.movelist[0] = '\0';
8764
8765                 SendProgramStatsToFrontend( cps, &programStats );
8766
8767                 return;
8768
8769             } else if (strncmp(message,"++",2) == 0) {
8770                 /* Crafty 9.29+ outputs this */
8771                 programStats.got_fail = 2;
8772                 return;
8773
8774             } else if (strncmp(message,"--",2) == 0) {
8775                 /* Crafty 9.29+ outputs this */
8776                 programStats.got_fail = 1;
8777                 return;
8778
8779             } else if (thinkOutput[0] != NULLCHAR &&
8780                        strncmp(message, "    ", 4) == 0) {
8781                 unsigned message_len;
8782
8783                 p = message;
8784                 while (*p && *p == ' ') p++;
8785
8786                 message_len = strlen( p );
8787
8788                 /* [AS] Avoid buffer overflow */
8789                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8790                     strcat(thinkOutput, " ");
8791                     strcat(thinkOutput, p);
8792                 }
8793
8794                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8795                     strcat(programStats.movelist, " ");
8796                     strcat(programStats.movelist, p);
8797                 }
8798
8799                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8800                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8801                     DisplayMove(currentMove - 1);
8802                 }
8803                 return;
8804             }
8805         }
8806         else {
8807             buf1[0] = NULLCHAR;
8808
8809             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8810                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8811             {
8812                 ChessProgramStats cpstats;
8813
8814                 if (plyext != ' ' && plyext != '\t') {
8815                     time *= 100;
8816                 }
8817
8818                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8819                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8820                     curscore = -curscore;
8821                 }
8822
8823                 cpstats.depth = plylev;
8824                 cpstats.nodes = nodes;
8825                 cpstats.time = time;
8826                 cpstats.score = curscore;
8827                 cpstats.got_only_move = 0;
8828                 cpstats.movelist[0] = '\0';
8829
8830                 if (buf1[0] != NULLCHAR) {
8831                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8832                 }
8833
8834                 cpstats.ok_to_send = 0;
8835                 cpstats.line_is_book = 0;
8836                 cpstats.nr_moves = 0;
8837                 cpstats.moves_left = 0;
8838
8839                 SendProgramStatsToFrontend( cps, &cpstats );
8840             }
8841         }
8842     }
8843 }
8844
8845
8846 /* Parse a game score from the character string "game", and
8847    record it as the history of the current game.  The game
8848    score is NOT assumed to start from the standard position.
8849    The display is not updated in any way.
8850    */
8851 void
8852 ParseGameHistory (char *game)
8853 {
8854     ChessMove moveType;
8855     int fromX, fromY, toX, toY, boardIndex;
8856     char promoChar;
8857     char *p, *q;
8858     char buf[MSG_SIZ];
8859
8860     if (appData.debugMode)
8861       fprintf(debugFP, "Parsing game history: %s\n", game);
8862
8863     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8864     gameInfo.site = StrSave(appData.icsHost);
8865     gameInfo.date = PGNDate();
8866     gameInfo.round = StrSave("-");
8867
8868     /* Parse out names of players */
8869     while (*game == ' ') game++;
8870     p = buf;
8871     while (*game != ' ') *p++ = *game++;
8872     *p = NULLCHAR;
8873     gameInfo.white = StrSave(buf);
8874     while (*game == ' ') game++;
8875     p = buf;
8876     while (*game != ' ' && *game != '\n') *p++ = *game++;
8877     *p = NULLCHAR;
8878     gameInfo.black = StrSave(buf);
8879
8880     /* Parse moves */
8881     boardIndex = blackPlaysFirst ? 1 : 0;
8882     yynewstr(game);
8883     for (;;) {
8884         yyboardindex = boardIndex;
8885         moveType = (ChessMove) Myylex();
8886         switch (moveType) {
8887           case IllegalMove:             /* maybe suicide chess, etc. */
8888   if (appData.debugMode) {
8889     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8890     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8891     setbuf(debugFP, NULL);
8892   }
8893           case WhitePromotion:
8894           case BlackPromotion:
8895           case WhiteNonPromotion:
8896           case BlackNonPromotion:
8897           case NormalMove:
8898           case WhiteCapturesEnPassant:
8899           case BlackCapturesEnPassant:
8900           case WhiteKingSideCastle:
8901           case WhiteQueenSideCastle:
8902           case BlackKingSideCastle:
8903           case BlackQueenSideCastle:
8904           case WhiteKingSideCastleWild:
8905           case WhiteQueenSideCastleWild:
8906           case BlackKingSideCastleWild:
8907           case BlackQueenSideCastleWild:
8908           /* PUSH Fabien */
8909           case WhiteHSideCastleFR:
8910           case WhiteASideCastleFR:
8911           case BlackHSideCastleFR:
8912           case BlackASideCastleFR:
8913           /* POP Fabien */
8914             fromX = currentMoveString[0] - AAA;
8915             fromY = currentMoveString[1] - ONE;
8916             toX = currentMoveString[2] - AAA;
8917             toY = currentMoveString[3] - ONE;
8918             promoChar = currentMoveString[4];
8919             break;
8920           case WhiteDrop:
8921           case BlackDrop:
8922             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8923             fromX = moveType == WhiteDrop ?
8924               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8925             (int) CharToPiece(ToLower(currentMoveString[0]));
8926             fromY = DROP_RANK;
8927             toX = currentMoveString[2] - AAA;
8928             toY = currentMoveString[3] - ONE;
8929             promoChar = NULLCHAR;
8930             break;
8931           case AmbiguousMove:
8932             /* bug? */
8933             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8934   if (appData.debugMode) {
8935     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8936     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8937     setbuf(debugFP, NULL);
8938   }
8939             DisplayError(buf, 0);
8940             return;
8941           case ImpossibleMove:
8942             /* bug? */
8943             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8944   if (appData.debugMode) {
8945     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8946     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8947     setbuf(debugFP, NULL);
8948   }
8949             DisplayError(buf, 0);
8950             return;
8951           case EndOfFile:
8952             if (boardIndex < backwardMostMove) {
8953                 /* Oops, gap.  How did that happen? */
8954                 DisplayError(_("Gap in move list"), 0);
8955                 return;
8956             }
8957             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8958             if (boardIndex > forwardMostMove) {
8959                 forwardMostMove = boardIndex;
8960             }
8961             return;
8962           case ElapsedTime:
8963             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8964                 strcat(parseList[boardIndex-1], " ");
8965                 strcat(parseList[boardIndex-1], yy_text);
8966             }
8967             continue;
8968           case Comment:
8969           case PGNTag:
8970           case NAG:
8971           default:
8972             /* ignore */
8973             continue;
8974           case WhiteWins:
8975           case BlackWins:
8976           case GameIsDrawn:
8977           case GameUnfinished:
8978             if (gameMode == IcsExamining) {
8979                 if (boardIndex < backwardMostMove) {
8980                     /* Oops, gap.  How did that happen? */
8981                     return;
8982                 }
8983                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8984                 return;
8985             }
8986             gameInfo.result = moveType;
8987             p = strchr(yy_text, '{');
8988             if (p == NULL) p = strchr(yy_text, '(');
8989             if (p == NULL) {
8990                 p = yy_text;
8991                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8992             } else {
8993                 q = strchr(p, *p == '{' ? '}' : ')');
8994                 if (q != NULL) *q = NULLCHAR;
8995                 p++;
8996             }
8997             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8998             gameInfo.resultDetails = StrSave(p);
8999             continue;
9000         }
9001         if (boardIndex >= forwardMostMove &&
9002             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9003             backwardMostMove = blackPlaysFirst ? 1 : 0;
9004             return;
9005         }
9006         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9007                                  fromY, fromX, toY, toX, promoChar,
9008                                  parseList[boardIndex]);
9009         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9010         /* currentMoveString is set as a side-effect of yylex */
9011         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9012         strcat(moveList[boardIndex], "\n");
9013         boardIndex++;
9014         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9015         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9016           case MT_NONE:
9017           case MT_STALEMATE:
9018           default:
9019             break;
9020           case MT_CHECK:
9021             if(gameInfo.variant != VariantShogi)
9022                 strcat(parseList[boardIndex - 1], "+");
9023             break;
9024           case MT_CHECKMATE:
9025           case MT_STAINMATE:
9026             strcat(parseList[boardIndex - 1], "#");
9027             break;
9028         }
9029     }
9030 }
9031
9032
9033 /* Apply a move to the given board  */
9034 void
9035 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9036 {
9037   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9038   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9039
9040     /* [HGM] compute & store e.p. status and castling rights for new position */
9041     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9042
9043       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9044       oldEP = (signed char)board[EP_STATUS];
9045       board[EP_STATUS] = EP_NONE;
9046
9047   if (fromY == DROP_RANK) {
9048         /* must be first */
9049         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9050             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9051             return;
9052         }
9053         piece = board[toY][toX] = (ChessSquare) fromX;
9054   } else {
9055       int i;
9056
9057       if( board[toY][toX] != EmptySquare )
9058            board[EP_STATUS] = EP_CAPTURE;
9059
9060       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9061            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9062                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9063       } else
9064       if( board[fromY][fromX] == WhitePawn ) {
9065            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9066                board[EP_STATUS] = EP_PAWN_MOVE;
9067            if( toY-fromY==2) {
9068                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9069                         gameInfo.variant != VariantBerolina || toX < fromX)
9070                       board[EP_STATUS] = toX | berolina;
9071                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9072                         gameInfo.variant != VariantBerolina || toX > fromX)
9073                       board[EP_STATUS] = toX;
9074            }
9075       } else
9076       if( board[fromY][fromX] == BlackPawn ) {
9077            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9078                board[EP_STATUS] = EP_PAWN_MOVE;
9079            if( toY-fromY== -2) {
9080                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9081                         gameInfo.variant != VariantBerolina || toX < fromX)
9082                       board[EP_STATUS] = toX | berolina;
9083                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9084                         gameInfo.variant != VariantBerolina || toX > fromX)
9085                       board[EP_STATUS] = toX;
9086            }
9087        }
9088
9089        for(i=0; i<nrCastlingRights; i++) {
9090            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9091               board[CASTLING][i] == toX   && castlingRank[i] == toY
9092              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9093        }
9094
9095      if (fromX == toX && fromY == toY) return;
9096
9097      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9098      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9099      if(gameInfo.variant == VariantKnightmate)
9100          king += (int) WhiteUnicorn - (int) WhiteKing;
9101
9102     /* Code added by Tord: */
9103     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9104     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9105         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9106       board[fromY][fromX] = EmptySquare;
9107       board[toY][toX] = EmptySquare;
9108       if((toX > fromX) != (piece == WhiteRook)) {
9109         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9110       } else {
9111         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9112       }
9113     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9114                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9115       board[fromY][fromX] = EmptySquare;
9116       board[toY][toX] = EmptySquare;
9117       if((toX > fromX) != (piece == BlackRook)) {
9118         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9119       } else {
9120         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9121       }
9122     /* End of code added by Tord */
9123
9124     } else if (board[fromY][fromX] == king
9125         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9126         && toY == fromY && toX > fromX+1) {
9127         board[fromY][fromX] = EmptySquare;
9128         board[toY][toX] = king;
9129         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9130         board[fromY][BOARD_RGHT-1] = EmptySquare;
9131     } else if (board[fromY][fromX] == king
9132         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9133                && toY == fromY && toX < fromX-1) {
9134         board[fromY][fromX] = EmptySquare;
9135         board[toY][toX] = king;
9136         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9137         board[fromY][BOARD_LEFT] = EmptySquare;
9138     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9139                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9140                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9141                ) {
9142         /* white pawn promotion */
9143         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9144         if(gameInfo.variant==VariantBughouse ||
9145            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9146             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9147         board[fromY][fromX] = EmptySquare;
9148     } else if ((fromY >= BOARD_HEIGHT>>1)
9149                && (toX != fromX)
9150                && gameInfo.variant != VariantXiangqi
9151                && gameInfo.variant != VariantBerolina
9152                && (board[fromY][fromX] == WhitePawn)
9153                && (board[toY][toX] == EmptySquare)) {
9154         board[fromY][fromX] = EmptySquare;
9155         board[toY][toX] = WhitePawn;
9156         captured = board[toY - 1][toX];
9157         board[toY - 1][toX] = EmptySquare;
9158     } else if ((fromY == BOARD_HEIGHT-4)
9159                && (toX == fromX)
9160                && gameInfo.variant == VariantBerolina
9161                && (board[fromY][fromX] == WhitePawn)
9162                && (board[toY][toX] == EmptySquare)) {
9163         board[fromY][fromX] = EmptySquare;
9164         board[toY][toX] = WhitePawn;
9165         if(oldEP & EP_BEROLIN_A) {
9166                 captured = board[fromY][fromX-1];
9167                 board[fromY][fromX-1] = EmptySquare;
9168         }else{  captured = board[fromY][fromX+1];
9169                 board[fromY][fromX+1] = EmptySquare;
9170         }
9171     } else if (board[fromY][fromX] == king
9172         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9173                && toY == fromY && toX > fromX+1) {
9174         board[fromY][fromX] = EmptySquare;
9175         board[toY][toX] = king;
9176         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9177         board[fromY][BOARD_RGHT-1] = EmptySquare;
9178     } else if (board[fromY][fromX] == king
9179         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9180                && toY == fromY && toX < fromX-1) {
9181         board[fromY][fromX] = EmptySquare;
9182         board[toY][toX] = king;
9183         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9184         board[fromY][BOARD_LEFT] = EmptySquare;
9185     } else if (fromY == 7 && fromX == 3
9186                && board[fromY][fromX] == BlackKing
9187                && toY == 7 && toX == 5) {
9188         board[fromY][fromX] = EmptySquare;
9189         board[toY][toX] = BlackKing;
9190         board[fromY][7] = EmptySquare;
9191         board[toY][4] = BlackRook;
9192     } else if (fromY == 7 && fromX == 3
9193                && board[fromY][fromX] == BlackKing
9194                && toY == 7 && toX == 1) {
9195         board[fromY][fromX] = EmptySquare;
9196         board[toY][toX] = BlackKing;
9197         board[fromY][0] = EmptySquare;
9198         board[toY][2] = BlackRook;
9199     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9200                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9201                && toY < promoRank && promoChar
9202                ) {
9203         /* black pawn promotion */
9204         board[toY][toX] = CharToPiece(ToLower(promoChar));
9205         if(gameInfo.variant==VariantBughouse ||
9206            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9207             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9208         board[fromY][fromX] = EmptySquare;
9209     } else if ((fromY < BOARD_HEIGHT>>1)
9210                && (toX != fromX)
9211                && gameInfo.variant != VariantXiangqi
9212                && gameInfo.variant != VariantBerolina
9213                && (board[fromY][fromX] == BlackPawn)
9214                && (board[toY][toX] == EmptySquare)) {
9215         board[fromY][fromX] = EmptySquare;
9216         board[toY][toX] = BlackPawn;
9217         captured = board[toY + 1][toX];
9218         board[toY + 1][toX] = EmptySquare;
9219     } else if ((fromY == 3)
9220                && (toX == fromX)
9221                && gameInfo.variant == VariantBerolina
9222                && (board[fromY][fromX] == BlackPawn)
9223                && (board[toY][toX] == EmptySquare)) {
9224         board[fromY][fromX] = EmptySquare;
9225         board[toY][toX] = BlackPawn;
9226         if(oldEP & EP_BEROLIN_A) {
9227                 captured = board[fromY][fromX-1];
9228                 board[fromY][fromX-1] = EmptySquare;
9229         }else{  captured = board[fromY][fromX+1];
9230                 board[fromY][fromX+1] = EmptySquare;
9231         }
9232     } else {
9233         board[toY][toX] = board[fromY][fromX];
9234         board[fromY][fromX] = EmptySquare;
9235     }
9236   }
9237
9238     if (gameInfo.holdingsWidth != 0) {
9239
9240       /* !!A lot more code needs to be written to support holdings  */
9241       /* [HGM] OK, so I have written it. Holdings are stored in the */
9242       /* penultimate board files, so they are automaticlly stored   */
9243       /* in the game history.                                       */
9244       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9245                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9246         /* Delete from holdings, by decreasing count */
9247         /* and erasing image if necessary            */
9248         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9249         if(p < (int) BlackPawn) { /* white drop */
9250              p -= (int)WhitePawn;
9251                  p = PieceToNumber((ChessSquare)p);
9252              if(p >= gameInfo.holdingsSize) p = 0;
9253              if(--board[p][BOARD_WIDTH-2] <= 0)
9254                   board[p][BOARD_WIDTH-1] = EmptySquare;
9255              if((int)board[p][BOARD_WIDTH-2] < 0)
9256                         board[p][BOARD_WIDTH-2] = 0;
9257         } else {                  /* black drop */
9258              p -= (int)BlackPawn;
9259                  p = PieceToNumber((ChessSquare)p);
9260              if(p >= gameInfo.holdingsSize) p = 0;
9261              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9262                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9263              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9264                         board[BOARD_HEIGHT-1-p][1] = 0;
9265         }
9266       }
9267       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9268           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9269         /* [HGM] holdings: Add to holdings, if holdings exist */
9270         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9271                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9272                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9273         }
9274         p = (int) captured;
9275         if (p >= (int) BlackPawn) {
9276           p -= (int)BlackPawn;
9277           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9278                   /* in Shogi restore piece to its original  first */
9279                   captured = (ChessSquare) (DEMOTED captured);
9280                   p = DEMOTED p;
9281           }
9282           p = PieceToNumber((ChessSquare)p);
9283           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9284           board[p][BOARD_WIDTH-2]++;
9285           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9286         } else {
9287           p -= (int)WhitePawn;
9288           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9289                   captured = (ChessSquare) (DEMOTED captured);
9290                   p = DEMOTED p;
9291           }
9292           p = PieceToNumber((ChessSquare)p);
9293           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9294           board[BOARD_HEIGHT-1-p][1]++;
9295           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9296         }
9297       }
9298     } else if (gameInfo.variant == VariantAtomic) {
9299       if (captured != EmptySquare) {
9300         int y, x;
9301         for (y = toY-1; y <= toY+1; y++) {
9302           for (x = toX-1; x <= toX+1; x++) {
9303             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9304                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9305               board[y][x] = EmptySquare;
9306             }
9307           }
9308         }
9309         board[toY][toX] = EmptySquare;
9310       }
9311     }
9312     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9313         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9314     } else
9315     if(promoChar == '+') {
9316         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9317         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9318     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9319         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9320     }
9321     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9322                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9323         // [HGM] superchess: take promotion piece out of holdings
9324         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9325         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9326             if(!--board[k][BOARD_WIDTH-2])
9327                 board[k][BOARD_WIDTH-1] = EmptySquare;
9328         } else {
9329             if(!--board[BOARD_HEIGHT-1-k][1])
9330                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9331         }
9332     }
9333
9334 }
9335
9336 /* Updates forwardMostMove */
9337 void
9338 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9339 {
9340 //    forwardMostMove++; // [HGM] bare: moved downstream
9341
9342     (void) CoordsToAlgebraic(boards[forwardMostMove],
9343                              PosFlags(forwardMostMove),
9344                              fromY, fromX, toY, toX, promoChar,
9345                              parseList[forwardMostMove]);
9346
9347     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9348         int timeLeft; static int lastLoadFlag=0; int king, piece;
9349         piece = boards[forwardMostMove][fromY][fromX];
9350         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9351         if(gameInfo.variant == VariantKnightmate)
9352             king += (int) WhiteUnicorn - (int) WhiteKing;
9353         if(forwardMostMove == 0) {
9354             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9355                 fprintf(serverMoves, "%s;", UserName());
9356             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9357                 fprintf(serverMoves, "%s;", second.tidy);
9358             fprintf(serverMoves, "%s;", first.tidy);
9359             if(gameMode == MachinePlaysWhite)
9360                 fprintf(serverMoves, "%s;", UserName());
9361             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9362                 fprintf(serverMoves, "%s;", second.tidy);
9363         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9364         lastLoadFlag = loadFlag;
9365         // print base move
9366         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9367         // print castling suffix
9368         if( toY == fromY && piece == king ) {
9369             if(toX-fromX > 1)
9370                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9371             if(fromX-toX >1)
9372                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9373         }
9374         // e.p. suffix
9375         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9376              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9377              boards[forwardMostMove][toY][toX] == EmptySquare
9378              && fromX != toX && fromY != toY)
9379                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9380         // promotion suffix
9381         if(promoChar != NULLCHAR)
9382                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9383         if(!loadFlag) {
9384                 char buf[MOVE_LEN*2], *p; int len;
9385             fprintf(serverMoves, "/%d/%d",
9386                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9387             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9388             else                      timeLeft = blackTimeRemaining/1000;
9389             fprintf(serverMoves, "/%d", timeLeft);
9390                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9391                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9392                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9393             fprintf(serverMoves, "/%s", buf);
9394         }
9395         fflush(serverMoves);
9396     }
9397
9398     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9399         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9400       return;
9401     }
9402     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9403     if (commentList[forwardMostMove+1] != NULL) {
9404         free(commentList[forwardMostMove+1]);
9405         commentList[forwardMostMove+1] = NULL;
9406     }
9407     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9408     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9409     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9410     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9411     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9412     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9413     adjustedClock = FALSE;
9414     gameInfo.result = GameUnfinished;
9415     if (gameInfo.resultDetails != NULL) {
9416         free(gameInfo.resultDetails);
9417         gameInfo.resultDetails = NULL;
9418     }
9419     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9420                               moveList[forwardMostMove - 1]);
9421     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9422       case MT_NONE:
9423       case MT_STALEMATE:
9424       default:
9425         break;
9426       case MT_CHECK:
9427         if(gameInfo.variant != VariantShogi)
9428             strcat(parseList[forwardMostMove - 1], "+");
9429         break;
9430       case MT_CHECKMATE:
9431       case MT_STAINMATE:
9432         strcat(parseList[forwardMostMove - 1], "#");
9433         break;
9434     }
9435     if (appData.debugMode) {
9436         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9437     }
9438
9439 }
9440
9441 /* Updates currentMove if not pausing */
9442 void
9443 ShowMove (int fromX, int fromY, int toX, int toY)
9444 {
9445     int instant = (gameMode == PlayFromGameFile) ?
9446         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9447     if(appData.noGUI) return;
9448     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9449         if (!instant) {
9450             if (forwardMostMove == currentMove + 1) {
9451                 AnimateMove(boards[forwardMostMove - 1],
9452                             fromX, fromY, toX, toY);
9453             }
9454             if (appData.highlightLastMove) {
9455                 SetHighlights(fromX, fromY, toX, toY);
9456             }
9457         }
9458         currentMove = forwardMostMove;
9459     }
9460
9461     if (instant) return;
9462
9463     DisplayMove(currentMove - 1);
9464     DrawPosition(FALSE, boards[currentMove]);
9465     DisplayBothClocks();
9466     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9467 }
9468
9469 void
9470 SendEgtPath (ChessProgramState *cps)
9471 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9472         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9473
9474         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9475
9476         while(*p) {
9477             char c, *q = name+1, *r, *s;
9478
9479             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9480             while(*p && *p != ',') *q++ = *p++;
9481             *q++ = ':'; *q = 0;
9482             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9483                 strcmp(name, ",nalimov:") == 0 ) {
9484                 // take nalimov path from the menu-changeable option first, if it is defined
9485               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9486                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9487             } else
9488             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9489                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9490                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9491                 s = r = StrStr(s, ":") + 1; // beginning of path info
9492                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9493                 c = *r; *r = 0;             // temporarily null-terminate path info
9494                     *--q = 0;               // strip of trailig ':' from name
9495                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9496                 *r = c;
9497                 SendToProgram(buf,cps);     // send egtbpath command for this format
9498             }
9499             if(*p == ',') p++; // read away comma to position for next format name
9500         }
9501 }
9502
9503 void
9504 InitChessProgram (ChessProgramState *cps, int setup)
9505 /* setup needed to setup FRC opening position */
9506 {
9507     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9508     if (appData.noChessProgram) return;
9509     hintRequested = FALSE;
9510     bookRequested = FALSE;
9511
9512     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9513     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9514     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9515     if(cps->memSize) { /* [HGM] memory */
9516       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9517         SendToProgram(buf, cps);
9518     }
9519     SendEgtPath(cps); /* [HGM] EGT */
9520     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9521       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9522         SendToProgram(buf, cps);
9523     }
9524
9525     SendToProgram(cps->initString, cps);
9526     if (gameInfo.variant != VariantNormal &&
9527         gameInfo.variant != VariantLoadable
9528         /* [HGM] also send variant if board size non-standard */
9529         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9530                                             ) {
9531       char *v = VariantName(gameInfo.variant);
9532       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9533         /* [HGM] in protocol 1 we have to assume all variants valid */
9534         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9535         DisplayFatalError(buf, 0, 1);
9536         return;
9537       }
9538
9539       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9540       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9541       if( gameInfo.variant == VariantXiangqi )
9542            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9543       if( gameInfo.variant == VariantShogi )
9544            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9545       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9546            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9547       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9548           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9549            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9550       if( gameInfo.variant == VariantCourier )
9551            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9552       if( gameInfo.variant == VariantSuper )
9553            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9554       if( gameInfo.variant == VariantGreat )
9555            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9556       if( gameInfo.variant == VariantSChess )
9557            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9558       if( gameInfo.variant == VariantGrand )
9559            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9560
9561       if(overruled) {
9562         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9563                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9564            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9565            if(StrStr(cps->variants, b) == NULL) {
9566                // specific sized variant not known, check if general sizing allowed
9567                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9568                    if(StrStr(cps->variants, "boardsize") == NULL) {
9569                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9570                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9571                        DisplayFatalError(buf, 0, 1);
9572                        return;
9573                    }
9574                    /* [HGM] here we really should compare with the maximum supported board size */
9575                }
9576            }
9577       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9578       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9579       SendToProgram(buf, cps);
9580     }
9581     currentlyInitializedVariant = gameInfo.variant;
9582
9583     /* [HGM] send opening position in FRC to first engine */
9584     if(setup) {
9585           SendToProgram("force\n", cps);
9586           SendBoard(cps, 0);
9587           /* engine is now in force mode! Set flag to wake it up after first move. */
9588           setboardSpoiledMachineBlack = 1;
9589     }
9590
9591     if (cps->sendICS) {
9592       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9593       SendToProgram(buf, cps);
9594     }
9595     cps->maybeThinking = FALSE;
9596     cps->offeredDraw = 0;
9597     if (!appData.icsActive) {
9598         SendTimeControl(cps, movesPerSession, timeControl,
9599                         timeIncrement, appData.searchDepth,
9600                         searchTime);
9601     }
9602     if (appData.showThinking
9603         // [HGM] thinking: four options require thinking output to be sent
9604         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9605                                 ) {
9606         SendToProgram("post\n", cps);
9607     }
9608     SendToProgram("hard\n", cps);
9609     if (!appData.ponderNextMove) {
9610         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9611            it without being sure what state we are in first.  "hard"
9612            is not a toggle, so that one is OK.
9613          */
9614         SendToProgram("easy\n", cps);
9615     }
9616     if (cps->usePing) {
9617       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9618       SendToProgram(buf, cps);
9619     }
9620     cps->initDone = TRUE;
9621     ClearEngineOutputPane(cps == &second);
9622 }
9623
9624
9625 void
9626 StartChessProgram (ChessProgramState *cps)
9627 {
9628     char buf[MSG_SIZ];
9629     int err;
9630
9631     if (appData.noChessProgram) return;
9632     cps->initDone = FALSE;
9633
9634     if (strcmp(cps->host, "localhost") == 0) {
9635         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9636     } else if (*appData.remoteShell == NULLCHAR) {
9637         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9638     } else {
9639         if (*appData.remoteUser == NULLCHAR) {
9640           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9641                     cps->program);
9642         } else {
9643           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9644                     cps->host, appData.remoteUser, cps->program);
9645         }
9646         err = StartChildProcess(buf, "", &cps->pr);
9647     }
9648
9649     if (err != 0) {
9650       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9651         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9652         if(cps != &first) return;
9653         appData.noChessProgram = TRUE;
9654         ThawUI();
9655         SetNCPMode();
9656 //      DisplayFatalError(buf, err, 1);
9657 //      cps->pr = NoProc;
9658 //      cps->isr = NULL;
9659         return;
9660     }
9661
9662     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9663     if (cps->protocolVersion > 1) {
9664       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9665       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9666       cps->comboCnt = 0;  //                and values of combo boxes
9667       SendToProgram(buf, cps);
9668     } else {
9669       SendToProgram("xboard\n", cps);
9670     }
9671 }
9672
9673 void
9674 TwoMachinesEventIfReady P((void))
9675 {
9676   static int curMess = 0;
9677   if (first.lastPing != first.lastPong) {
9678     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9679     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9680     return;
9681   }
9682   if (second.lastPing != second.lastPong) {
9683     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9684     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9685     return;
9686   }
9687   DisplayMessage("", ""); curMess = 0;
9688   ThawUI();
9689   TwoMachinesEvent();
9690 }
9691
9692 char *
9693 MakeName (char *template)
9694 {
9695     time_t clock;
9696     struct tm *tm;
9697     static char buf[MSG_SIZ];
9698     char *p = buf;
9699     int i;
9700
9701     clock = time((time_t *)NULL);
9702     tm = localtime(&clock);
9703
9704     while(*p++ = *template++) if(p[-1] == '%') {
9705         switch(*template++) {
9706           case 0:   *p = 0; return buf;
9707           case 'Y': i = tm->tm_year+1900; break;
9708           case 'y': i = tm->tm_year-100; break;
9709           case 'M': i = tm->tm_mon+1; break;
9710           case 'd': i = tm->tm_mday; break;
9711           case 'h': i = tm->tm_hour; break;
9712           case 'm': i = tm->tm_min; break;
9713           case 's': i = tm->tm_sec; break;
9714           default:  i = 0;
9715         }
9716         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9717     }
9718     return buf;
9719 }
9720
9721 int
9722 CountPlayers (char *p)
9723 {
9724     int n = 0;
9725     while(p = strchr(p, '\n')) p++, n++; // count participants
9726     return n;
9727 }
9728
9729 FILE *
9730 WriteTourneyFile (char *results, FILE *f)
9731 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9732     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9733     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9734         // create a file with tournament description
9735         fprintf(f, "-participants {%s}\n", appData.participants);
9736         fprintf(f, "-seedBase %d\n", appData.seedBase);
9737         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9738         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9739         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9740         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9741         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9742         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9743         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9744         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9745         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9746         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9747         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9748         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9749         if(searchTime > 0)
9750                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9751         else {
9752                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9753                 fprintf(f, "-tc %s\n", appData.timeControl);
9754                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9755         }
9756         fprintf(f, "-results \"%s\"\n", results);
9757     }
9758     return f;
9759 }
9760
9761 #define MAXENGINES 1000
9762 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9763
9764 void
9765 Substitute (char *participants, int expunge)
9766 {
9767     int i, changed, changes=0, nPlayers=0;
9768     char *p, *q, *r, buf[MSG_SIZ];
9769     if(participants == NULL) return;
9770     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9771     r = p = participants; q = appData.participants;
9772     while(*p && *p == *q) {
9773         if(*p == '\n') r = p+1, nPlayers++;
9774         p++; q++;
9775     }
9776     if(*p) { // difference
9777         while(*p && *p++ != '\n');
9778         while(*q && *q++ != '\n');
9779       changed = nPlayers;
9780         changes = 1 + (strcmp(p, q) != 0);
9781     }
9782     if(changes == 1) { // a single engine mnemonic was changed
9783         q = r; while(*q) nPlayers += (*q++ == '\n');
9784         p = buf; while(*r && (*p = *r++) != '\n') p++;
9785         *p = NULLCHAR;
9786         NamesToList(firstChessProgramNames, command, mnemonic);
9787         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9788         if(mnemonic[i]) { // The substitute is valid
9789             FILE *f;
9790             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9791                 flock(fileno(f), LOCK_EX);
9792                 ParseArgsFromFile(f);
9793                 fseek(f, 0, SEEK_SET);
9794                 FREE(appData.participants); appData.participants = participants;
9795                 if(expunge) { // erase results of replaced engine
9796                     int len = strlen(appData.results), w, b, dummy;
9797                     for(i=0; i<len; i++) {
9798                         Pairing(i, nPlayers, &w, &b, &dummy);
9799                         if((w == changed || b == changed) && appData.results[i] == '*') {
9800                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9801                             fclose(f);
9802                             return;
9803                         }
9804                     }
9805                     for(i=0; i<len; i++) {
9806                         Pairing(i, nPlayers, &w, &b, &dummy);
9807                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9808                     }
9809                 }
9810                 WriteTourneyFile(appData.results, f);
9811                 fclose(f); // release lock
9812                 return;
9813             }
9814         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9815     }
9816     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9817     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9818     free(participants);
9819     return;
9820 }
9821
9822 int
9823 CreateTourney (char *name)
9824 {
9825         FILE *f;
9826         if(matchMode && strcmp(name, appData.tourneyFile)) {
9827              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9828         }
9829         if(name[0] == NULLCHAR) {
9830             if(appData.participants[0])
9831                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9832             return 0;
9833         }
9834         f = fopen(name, "r");
9835         if(f) { // file exists
9836             ASSIGN(appData.tourneyFile, name);
9837             ParseArgsFromFile(f); // parse it
9838         } else {
9839             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9840             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9841                 DisplayError(_("Not enough participants"), 0);
9842                 return 0;
9843             }
9844             ASSIGN(appData.tourneyFile, name);
9845             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9846             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9847         }
9848         fclose(f);
9849         appData.noChessProgram = FALSE;
9850         appData.clockMode = TRUE;
9851         SetGNUMode();
9852         return 1;
9853 }
9854
9855 void
9856 NamesToList (char *names, char **engineList, char **engineMnemonic)
9857 {
9858     char buf[MSG_SIZ], *p, *q;
9859     int i=1;
9860     while(*names) {
9861         p = names; q = buf;
9862         while(*p && *p != '\n') *q++ = *p++;
9863         *q = 0;
9864         if(engineList[i]) free(engineList[i]);
9865         engineList[i] = strdup(buf);
9866         if(*p == '\n') p++;
9867         TidyProgramName(engineList[i], "localhost", buf);
9868         if(engineMnemonic[i]) free(engineMnemonic[i]);
9869         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9870             strcat(buf, " (");
9871             sscanf(q + 8, "%s", buf + strlen(buf));
9872             strcat(buf, ")");
9873         }
9874         engineMnemonic[i] = strdup(buf);
9875         names = p; i++;
9876       if(i > MAXENGINES - 2) break;
9877     }
9878     engineList[i] = engineMnemonic[i] = NULL;
9879 }
9880
9881 // following implemented as macro to avoid type limitations
9882 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9883
9884 void
9885 SwapEngines (int n)
9886 {   // swap settings for first engine and other engine (so far only some selected options)
9887     int h;
9888     char *p;
9889     if(n == 0) return;
9890     SWAP(directory, p)
9891     SWAP(chessProgram, p)
9892     SWAP(isUCI, h)
9893     SWAP(hasOwnBookUCI, h)
9894     SWAP(protocolVersion, h)
9895     SWAP(reuse, h)
9896     SWAP(scoreIsAbsolute, h)
9897     SWAP(timeOdds, h)
9898     SWAP(logo, p)
9899     SWAP(pgnName, p)
9900     SWAP(pvSAN, h)
9901     SWAP(engOptions, p)
9902 }
9903
9904 void
9905 SetPlayer (int player)
9906 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9907     int i;
9908     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9909     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9910     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9911     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9912     if(mnemonic[i]) {
9913         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9914         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9915         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
9916         ParseArgsFromString(buf);
9917     }
9918     free(engineName);
9919 }
9920
9921 int
9922 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9923 {   // determine players from game number
9924     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9925
9926     if(appData.tourneyType == 0) {
9927         roundsPerCycle = (nPlayers - 1) | 1;
9928         pairingsPerRound = nPlayers / 2;
9929     } else if(appData.tourneyType > 0) {
9930         roundsPerCycle = nPlayers - appData.tourneyType;
9931         pairingsPerRound = appData.tourneyType;
9932     }
9933     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9934     gamesPerCycle = gamesPerRound * roundsPerCycle;
9935     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9936     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9937     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9938     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9939     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9940     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9941
9942     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9943     if(appData.roundSync) *syncInterval = gamesPerRound;
9944
9945     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9946
9947     if(appData.tourneyType == 0) {
9948         if(curPairing == (nPlayers-1)/2 ) {
9949             *whitePlayer = curRound;
9950             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9951         } else {
9952             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9953             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9954             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9955             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9956         }
9957     } else if(appData.tourneyType > 0) {
9958         *whitePlayer = curPairing;
9959         *blackPlayer = curRound + appData.tourneyType;
9960     }
9961
9962     // take care of white/black alternation per round. 
9963     // For cycles and games this is already taken care of by default, derived from matchGame!
9964     return curRound & 1;
9965 }
9966
9967 int
9968 NextTourneyGame (int nr, int *swapColors)
9969 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9970     char *p, *q;
9971     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9972     FILE *tf;
9973     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9974     tf = fopen(appData.tourneyFile, "r");
9975     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9976     ParseArgsFromFile(tf); fclose(tf);
9977     InitTimeControls(); // TC might be altered from tourney file
9978
9979     nPlayers = CountPlayers(appData.participants); // count participants
9980     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
9981     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9982
9983     if(syncInterval) {
9984         p = q = appData.results;
9985         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9986         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9987             DisplayMessage(_("Waiting for other game(s)"),"");
9988             waitingForGame = TRUE;
9989             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9990             return 0;
9991         }
9992         waitingForGame = FALSE;
9993     }
9994
9995     if(appData.tourneyType < 0) {
9996         if(nr>=0 && !pairingReceived) {
9997             char buf[1<<16];
9998             if(pairing.pr == NoProc) {
9999                 if(!appData.pairingEngine[0]) {
10000                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10001                     return 0;
10002                 }
10003                 StartChessProgram(&pairing); // starts the pairing engine
10004             }
10005             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10006             SendToProgram(buf, &pairing);
10007             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10008             SendToProgram(buf, &pairing);
10009             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10010         }
10011         pairingReceived = 0;                              // ... so we continue here 
10012         *swapColors = 0;
10013         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10014         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10015         matchGame = 1; roundNr = nr / syncInterval + 1;
10016     }
10017
10018     if(first.pr != NoProc || second.pr != NoProc) return 1; // engines already loaded
10019
10020     // redefine engines, engine dir, etc.
10021     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10022     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
10023     SwapEngines(1);
10024     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
10025     SwapEngines(1);         // and make that valid for second engine by swapping
10026     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10027     InitEngine(&second, 1);
10028     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10029     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10030     return 1;
10031 }
10032
10033 void
10034 NextMatchGame ()
10035 {   // performs game initialization that does not invoke engines, and then tries to start the game
10036     int res, firstWhite, swapColors = 0;
10037     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10038     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10039     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10040     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10041     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10042     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10043     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10044     Reset(FALSE, first.pr != NoProc);
10045     res = LoadGameOrPosition(matchGame); // setup game
10046     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10047     if(!res) return; // abort when bad game/pos file
10048     TwoMachinesEvent();
10049 }
10050
10051 void
10052 UserAdjudicationEvent (int result)
10053 {
10054     ChessMove gameResult = GameIsDrawn;
10055
10056     if( result > 0 ) {
10057         gameResult = WhiteWins;
10058     }
10059     else if( result < 0 ) {
10060         gameResult = BlackWins;
10061     }
10062
10063     if( gameMode == TwoMachinesPlay ) {
10064         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10065     }
10066 }
10067
10068
10069 // [HGM] save: calculate checksum of game to make games easily identifiable
10070 int
10071 StringCheckSum (char *s)
10072 {
10073         int i = 0;
10074         if(s==NULL) return 0;
10075         while(*s) i = i*259 + *s++;
10076         return i;
10077 }
10078
10079 int
10080 GameCheckSum ()
10081 {
10082         int i, sum=0;
10083         for(i=backwardMostMove; i<forwardMostMove; i++) {
10084                 sum += pvInfoList[i].depth;
10085                 sum += StringCheckSum(parseList[i]);
10086                 sum += StringCheckSum(commentList[i]);
10087                 sum *= 261;
10088         }
10089         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10090         return sum + StringCheckSum(commentList[i]);
10091 } // end of save patch
10092
10093 void
10094 GameEnds (ChessMove result, char *resultDetails, int whosays)
10095 {
10096     GameMode nextGameMode;
10097     int isIcsGame;
10098     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10099
10100     if(endingGame) return; /* [HGM] crash: forbid recursion */
10101     endingGame = 1;
10102     if(twoBoards) { // [HGM] dual: switch back to one board
10103         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10104         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10105     }
10106     if (appData.debugMode) {
10107       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10108               result, resultDetails ? resultDetails : "(null)", whosays);
10109     }
10110
10111     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10112
10113     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10114         /* If we are playing on ICS, the server decides when the
10115            game is over, but the engine can offer to draw, claim
10116            a draw, or resign.
10117          */
10118 #if ZIPPY
10119         if (appData.zippyPlay && first.initDone) {
10120             if (result == GameIsDrawn) {
10121                 /* In case draw still needs to be claimed */
10122                 SendToICS(ics_prefix);
10123                 SendToICS("draw\n");
10124             } else if (StrCaseStr(resultDetails, "resign")) {
10125                 SendToICS(ics_prefix);
10126                 SendToICS("resign\n");
10127             }
10128         }
10129 #endif
10130         endingGame = 0; /* [HGM] crash */
10131         return;
10132     }
10133
10134     /* If we're loading the game from a file, stop */
10135     if (whosays == GE_FILE) {
10136       (void) StopLoadGameTimer();
10137       gameFileFP = NULL;
10138     }
10139
10140     /* Cancel draw offers */
10141     first.offeredDraw = second.offeredDraw = 0;
10142
10143     /* If this is an ICS game, only ICS can really say it's done;
10144        if not, anyone can. */
10145     isIcsGame = (gameMode == IcsPlayingWhite ||
10146                  gameMode == IcsPlayingBlack ||
10147                  gameMode == IcsObserving    ||
10148                  gameMode == IcsExamining);
10149
10150     if (!isIcsGame || whosays == GE_ICS) {
10151         /* OK -- not an ICS game, or ICS said it was done */
10152         StopClocks();
10153         if (!isIcsGame && !appData.noChessProgram)
10154           SetUserThinkingEnables();
10155
10156         /* [HGM] if a machine claims the game end we verify this claim */
10157         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10158             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10159                 char claimer;
10160                 ChessMove trueResult = (ChessMove) -1;
10161
10162                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10163                                             first.twoMachinesColor[0] :
10164                                             second.twoMachinesColor[0] ;
10165
10166                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10167                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10168                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10169                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10170                 } else
10171                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10172                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10173                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10174                 } else
10175                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10176                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10177                 }
10178
10179                 // now verify win claims, but not in drop games, as we don't understand those yet
10180                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10181                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10182                     (result == WhiteWins && claimer == 'w' ||
10183                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10184                       if (appData.debugMode) {
10185                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10186                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10187                       }
10188                       if(result != trueResult) {
10189                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10190                               result = claimer == 'w' ? BlackWins : WhiteWins;
10191                               resultDetails = buf;
10192                       }
10193                 } else
10194                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10195                     && (forwardMostMove <= backwardMostMove ||
10196                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10197                         (claimer=='b')==(forwardMostMove&1))
10198                                                                                   ) {
10199                       /* [HGM] verify: draws that were not flagged are false claims */
10200                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10201                       result = claimer == 'w' ? BlackWins : WhiteWins;
10202                       resultDetails = buf;
10203                 }
10204                 /* (Claiming a loss is accepted no questions asked!) */
10205             }
10206             /* [HGM] bare: don't allow bare King to win */
10207             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10208                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10209                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10210                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10211                && result != GameIsDrawn)
10212             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10213                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10214                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10215                         if(p >= 0 && p <= (int)WhiteKing) k++;
10216                 }
10217                 if (appData.debugMode) {
10218                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10219                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10220                 }
10221                 if(k <= 1) {
10222                         result = GameIsDrawn;
10223                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10224                         resultDetails = buf;
10225                 }
10226             }
10227         }
10228
10229
10230         if(serverMoves != NULL && !loadFlag) { char c = '=';
10231             if(result==WhiteWins) c = '+';
10232             if(result==BlackWins) c = '-';
10233             if(resultDetails != NULL)
10234                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10235         }
10236         if (resultDetails != NULL) {
10237             gameInfo.result = result;
10238             gameInfo.resultDetails = StrSave(resultDetails);
10239
10240             /* display last move only if game was not loaded from file */
10241             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10242                 DisplayMove(currentMove - 1);
10243
10244             if (forwardMostMove != 0) {
10245                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10246                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10247                                                                 ) {
10248                     if (*appData.saveGameFile != NULLCHAR) {
10249                         SaveGameToFile(appData.saveGameFile, TRUE);
10250                     } else if (appData.autoSaveGames) {
10251                         AutoSaveGame();
10252                     }
10253                     if (*appData.savePositionFile != NULLCHAR) {
10254                         SavePositionToFile(appData.savePositionFile);
10255                     }
10256                 }
10257             }
10258
10259             /* Tell program how game ended in case it is learning */
10260             /* [HGM] Moved this to after saving the PGN, just in case */
10261             /* engine died and we got here through time loss. In that */
10262             /* case we will get a fatal error writing the pipe, which */
10263             /* would otherwise lose us the PGN.                       */
10264             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10265             /* output during GameEnds should never be fatal anymore   */
10266             if (gameMode == MachinePlaysWhite ||
10267                 gameMode == MachinePlaysBlack ||
10268                 gameMode == TwoMachinesPlay ||
10269                 gameMode == IcsPlayingWhite ||
10270                 gameMode == IcsPlayingBlack ||
10271                 gameMode == BeginningOfGame) {
10272                 char buf[MSG_SIZ];
10273                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10274                         resultDetails);
10275                 if (first.pr != NoProc) {
10276                     SendToProgram(buf, &first);
10277                 }
10278                 if (second.pr != NoProc &&
10279                     gameMode == TwoMachinesPlay) {
10280                     SendToProgram(buf, &second);
10281                 }
10282             }
10283         }
10284
10285         if (appData.icsActive) {
10286             if (appData.quietPlay &&
10287                 (gameMode == IcsPlayingWhite ||
10288                  gameMode == IcsPlayingBlack)) {
10289                 SendToICS(ics_prefix);
10290                 SendToICS("set shout 1\n");
10291             }
10292             nextGameMode = IcsIdle;
10293             ics_user_moved = FALSE;
10294             /* clean up premove.  It's ugly when the game has ended and the
10295              * premove highlights are still on the board.
10296              */
10297             if (gotPremove) {
10298               gotPremove = FALSE;
10299               ClearPremoveHighlights();
10300               DrawPosition(FALSE, boards[currentMove]);
10301             }
10302             if (whosays == GE_ICS) {
10303                 switch (result) {
10304                 case WhiteWins:
10305                     if (gameMode == IcsPlayingWhite)
10306                         PlayIcsWinSound();
10307                     else if(gameMode == IcsPlayingBlack)
10308                         PlayIcsLossSound();
10309                     break;
10310                 case BlackWins:
10311                     if (gameMode == IcsPlayingBlack)
10312                         PlayIcsWinSound();
10313                     else if(gameMode == IcsPlayingWhite)
10314                         PlayIcsLossSound();
10315                     break;
10316                 case GameIsDrawn:
10317                     PlayIcsDrawSound();
10318                     break;
10319                 default:
10320                     PlayIcsUnfinishedSound();
10321                 }
10322             }
10323         } else if (gameMode == EditGame ||
10324                    gameMode == PlayFromGameFile ||
10325                    gameMode == AnalyzeMode ||
10326                    gameMode == AnalyzeFile) {
10327             nextGameMode = gameMode;
10328         } else {
10329             nextGameMode = EndOfGame;
10330         }
10331         pausing = FALSE;
10332         ModeHighlight();
10333     } else {
10334         nextGameMode = gameMode;
10335     }
10336
10337     if (appData.noChessProgram) {
10338         gameMode = nextGameMode;
10339         ModeHighlight();
10340         endingGame = 0; /* [HGM] crash */
10341         return;
10342     }
10343
10344     if (first.reuse) {
10345         /* Put first chess program into idle state */
10346         if (first.pr != NoProc &&
10347             (gameMode == MachinePlaysWhite ||
10348              gameMode == MachinePlaysBlack ||
10349              gameMode == TwoMachinesPlay ||
10350              gameMode == IcsPlayingWhite ||
10351              gameMode == IcsPlayingBlack ||
10352              gameMode == BeginningOfGame)) {
10353             SendToProgram("force\n", &first);
10354             if (first.usePing) {
10355               char buf[MSG_SIZ];
10356               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10357               SendToProgram(buf, &first);
10358             }
10359         }
10360     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10361         /* Kill off first chess program */
10362         if (first.isr != NULL)
10363           RemoveInputSource(first.isr);
10364         first.isr = NULL;
10365
10366         if (first.pr != NoProc) {
10367             ExitAnalyzeMode();
10368             DoSleep( appData.delayBeforeQuit );
10369             SendToProgram("quit\n", &first);
10370             DoSleep( appData.delayAfterQuit );
10371             DestroyChildProcess(first.pr, first.useSigterm);
10372         }
10373         first.pr = NoProc;
10374     }
10375     if (second.reuse) {
10376         /* Put second chess program into idle state */
10377         if (second.pr != NoProc &&
10378             gameMode == TwoMachinesPlay) {
10379             SendToProgram("force\n", &second);
10380             if (second.usePing) {
10381               char buf[MSG_SIZ];
10382               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10383               SendToProgram(buf, &second);
10384             }
10385         }
10386     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10387         /* Kill off second chess program */
10388         if (second.isr != NULL)
10389           RemoveInputSource(second.isr);
10390         second.isr = NULL;
10391
10392         if (second.pr != NoProc) {
10393             DoSleep( appData.delayBeforeQuit );
10394             SendToProgram("quit\n", &second);
10395             DoSleep( appData.delayAfterQuit );
10396             DestroyChildProcess(second.pr, second.useSigterm);
10397         }
10398         second.pr = NoProc;
10399     }
10400
10401     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10402         char resChar = '=';
10403         switch (result) {
10404         case WhiteWins:
10405           resChar = '+';
10406           if (first.twoMachinesColor[0] == 'w') {
10407             first.matchWins++;
10408           } else {
10409             second.matchWins++;
10410           }
10411           break;
10412         case BlackWins:
10413           resChar = '-';
10414           if (first.twoMachinesColor[0] == 'b') {
10415             first.matchWins++;
10416           } else {
10417             second.matchWins++;
10418           }
10419           break;
10420         case GameUnfinished:
10421           resChar = ' ';
10422         default:
10423           break;
10424         }
10425
10426         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10427         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10428             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10429             ReserveGame(nextGame, resChar); // sets nextGame
10430             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10431             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10432         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10433
10434         if (nextGame <= appData.matchGames && !abortMatch) {
10435             gameMode = nextGameMode;
10436             matchGame = nextGame; // this will be overruled in tourney mode!
10437             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10438             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10439             endingGame = 0; /* [HGM] crash */
10440             return;
10441         } else {
10442             gameMode = nextGameMode;
10443             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10444                      first.tidy, second.tidy,
10445                      first.matchWins, second.matchWins,
10446                      appData.matchGames - (first.matchWins + second.matchWins));
10447             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10448             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10449             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10450             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10451                 first.twoMachinesColor = "black\n";
10452                 second.twoMachinesColor = "white\n";
10453             } else {
10454                 first.twoMachinesColor = "white\n";
10455                 second.twoMachinesColor = "black\n";
10456             }
10457         }
10458     }
10459     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10460         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10461       ExitAnalyzeMode();
10462     gameMode = nextGameMode;
10463     ModeHighlight();
10464     endingGame = 0;  /* [HGM] crash */
10465     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10466         if(matchMode == TRUE) { // match through command line: exit with or without popup
10467             if(ranking) {
10468                 ToNrEvent(forwardMostMove);
10469                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10470                 else ExitEvent(0);
10471             } else DisplayFatalError(buf, 0, 0);
10472         } else { // match through menu; just stop, with or without popup
10473             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10474             ModeHighlight();
10475             if(ranking){
10476                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10477             } else DisplayNote(buf);
10478       }
10479       if(ranking) free(ranking);
10480     }
10481 }
10482
10483 /* Assumes program was just initialized (initString sent).
10484    Leaves program in force mode. */
10485 void
10486 FeedMovesToProgram (ChessProgramState *cps, int upto)
10487 {
10488     int i;
10489
10490     if (appData.debugMode)
10491       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10492               startedFromSetupPosition ? "position and " : "",
10493               backwardMostMove, upto, cps->which);
10494     if(currentlyInitializedVariant != gameInfo.variant) {
10495       char buf[MSG_SIZ];
10496         // [HGM] variantswitch: make engine aware of new variant
10497         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10498                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10499         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10500         SendToProgram(buf, cps);
10501         currentlyInitializedVariant = gameInfo.variant;
10502     }
10503     SendToProgram("force\n", cps);
10504     if (startedFromSetupPosition) {
10505         SendBoard(cps, backwardMostMove);
10506     if (appData.debugMode) {
10507         fprintf(debugFP, "feedMoves\n");
10508     }
10509     }
10510     for (i = backwardMostMove; i < upto; i++) {
10511         SendMoveToProgram(i, cps);
10512     }
10513 }
10514
10515
10516 int
10517 ResurrectChessProgram ()
10518 {
10519      /* The chess program may have exited.
10520         If so, restart it and feed it all the moves made so far. */
10521     static int doInit = 0;
10522
10523     if (appData.noChessProgram) return 1;
10524
10525     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10526         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10527         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10528         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10529     } else {
10530         if (first.pr != NoProc) return 1;
10531         StartChessProgram(&first);
10532     }
10533     InitChessProgram(&first, FALSE);
10534     FeedMovesToProgram(&first, currentMove);
10535
10536     if (!first.sendTime) {
10537         /* can't tell gnuchess what its clock should read,
10538            so we bow to its notion. */
10539         ResetClocks();
10540         timeRemaining[0][currentMove] = whiteTimeRemaining;
10541         timeRemaining[1][currentMove] = blackTimeRemaining;
10542     }
10543
10544     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10545                 appData.icsEngineAnalyze) && first.analysisSupport) {
10546       SendToProgram("analyze\n", &first);
10547       first.analyzing = TRUE;
10548     }
10549     return 1;
10550 }
10551
10552 /*
10553  * Button procedures
10554  */
10555 void
10556 Reset (int redraw, int init)
10557 {
10558     int i;
10559
10560     if (appData.debugMode) {
10561         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10562                 redraw, init, gameMode);
10563     }
10564     CleanupTail(); // [HGM] vari: delete any stored variations
10565     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10566     pausing = pauseExamInvalid = FALSE;
10567     startedFromSetupPosition = blackPlaysFirst = FALSE;
10568     firstMove = TRUE;
10569     whiteFlag = blackFlag = FALSE;
10570     userOfferedDraw = FALSE;
10571     hintRequested = bookRequested = FALSE;
10572     first.maybeThinking = FALSE;
10573     second.maybeThinking = FALSE;
10574     first.bookSuspend = FALSE; // [HGM] book
10575     second.bookSuspend = FALSE;
10576     thinkOutput[0] = NULLCHAR;
10577     lastHint[0] = NULLCHAR;
10578     ClearGameInfo(&gameInfo);
10579     gameInfo.variant = StringToVariant(appData.variant);
10580     ics_user_moved = ics_clock_paused = FALSE;
10581     ics_getting_history = H_FALSE;
10582     ics_gamenum = -1;
10583     white_holding[0] = black_holding[0] = NULLCHAR;
10584     ClearProgramStats();
10585     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10586
10587     ResetFrontEnd();
10588     ClearHighlights();
10589     flipView = appData.flipView;
10590     ClearPremoveHighlights();
10591     gotPremove = FALSE;
10592     alarmSounded = FALSE;
10593
10594     GameEnds(EndOfFile, NULL, GE_PLAYER);
10595     if(appData.serverMovesName != NULL) {
10596         /* [HGM] prepare to make moves file for broadcasting */
10597         clock_t t = clock();
10598         if(serverMoves != NULL) fclose(serverMoves);
10599         serverMoves = fopen(appData.serverMovesName, "r");
10600         if(serverMoves != NULL) {
10601             fclose(serverMoves);
10602             /* delay 15 sec before overwriting, so all clients can see end */
10603             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10604         }
10605         serverMoves = fopen(appData.serverMovesName, "w");
10606     }
10607
10608     ExitAnalyzeMode();
10609     gameMode = BeginningOfGame;
10610     ModeHighlight();
10611     if(appData.icsActive) gameInfo.variant = VariantNormal;
10612     currentMove = forwardMostMove = backwardMostMove = 0;
10613     MarkTargetSquares(1);
10614     InitPosition(redraw);
10615     for (i = 0; i < MAX_MOVES; i++) {
10616         if (commentList[i] != NULL) {
10617             free(commentList[i]);
10618             commentList[i] = NULL;
10619         }
10620     }
10621     ResetClocks();
10622     timeRemaining[0][0] = whiteTimeRemaining;
10623     timeRemaining[1][0] = blackTimeRemaining;
10624
10625     if (first.pr == NoProc) {
10626         StartChessProgram(&first);
10627     }
10628     if (init) {
10629             InitChessProgram(&first, startedFromSetupPosition);
10630     }
10631     DisplayTitle("");
10632     DisplayMessage("", "");
10633     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10634     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10635 }
10636
10637 void
10638 AutoPlayGameLoop ()
10639 {
10640     for (;;) {
10641         if (!AutoPlayOneMove())
10642           return;
10643         if (matchMode || appData.timeDelay == 0)
10644           continue;
10645         if (appData.timeDelay < 0)
10646           return;
10647         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10648         break;
10649     }
10650 }
10651
10652
10653 int
10654 AutoPlayOneMove ()
10655 {
10656     int fromX, fromY, toX, toY;
10657
10658     if (appData.debugMode) {
10659       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10660     }
10661
10662     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10663       return FALSE;
10664
10665     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10666       pvInfoList[currentMove].depth = programStats.depth;
10667       pvInfoList[currentMove].score = programStats.score;
10668       pvInfoList[currentMove].time  = 0;
10669       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10670     }
10671
10672     if (currentMove >= forwardMostMove) {
10673       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10674 //      gameMode = EndOfGame;
10675 //      ModeHighlight();
10676
10677       /* [AS] Clear current move marker at the end of a game */
10678       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10679
10680       return FALSE;
10681     }
10682
10683     toX = moveList[currentMove][2] - AAA;
10684     toY = moveList[currentMove][3] - ONE;
10685
10686     if (moveList[currentMove][1] == '@') {
10687         if (appData.highlightLastMove) {
10688             SetHighlights(-1, -1, toX, toY);
10689         }
10690     } else {
10691         fromX = moveList[currentMove][0] - AAA;
10692         fromY = moveList[currentMove][1] - ONE;
10693
10694         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10695
10696         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10697
10698         if (appData.highlightLastMove) {
10699             SetHighlights(fromX, fromY, toX, toY);
10700         }
10701     }
10702     DisplayMove(currentMove);
10703     SendMoveToProgram(currentMove++, &first);
10704     DisplayBothClocks();
10705     DrawPosition(FALSE, boards[currentMove]);
10706     // [HGM] PV info: always display, routine tests if empty
10707     DisplayComment(currentMove - 1, commentList[currentMove]);
10708     return TRUE;
10709 }
10710
10711
10712 int
10713 LoadGameOneMove (ChessMove readAhead)
10714 {
10715     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10716     char promoChar = NULLCHAR;
10717     ChessMove moveType;
10718     char move[MSG_SIZ];
10719     char *p, *q;
10720
10721     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10722         gameMode != AnalyzeMode && gameMode != Training) {
10723         gameFileFP = NULL;
10724         return FALSE;
10725     }
10726
10727     yyboardindex = forwardMostMove;
10728     if (readAhead != EndOfFile) {
10729       moveType = readAhead;
10730     } else {
10731       if (gameFileFP == NULL)
10732           return FALSE;
10733       moveType = (ChessMove) Myylex();
10734     }
10735
10736     done = FALSE;
10737     switch (moveType) {
10738       case Comment:
10739         if (appData.debugMode)
10740           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10741         p = yy_text;
10742
10743         /* append the comment but don't display it */
10744         AppendComment(currentMove, p, FALSE);
10745         return TRUE;
10746
10747       case WhiteCapturesEnPassant:
10748       case BlackCapturesEnPassant:
10749       case WhitePromotion:
10750       case BlackPromotion:
10751       case WhiteNonPromotion:
10752       case BlackNonPromotion:
10753       case NormalMove:
10754       case WhiteKingSideCastle:
10755       case WhiteQueenSideCastle:
10756       case BlackKingSideCastle:
10757       case BlackQueenSideCastle:
10758       case WhiteKingSideCastleWild:
10759       case WhiteQueenSideCastleWild:
10760       case BlackKingSideCastleWild:
10761       case BlackQueenSideCastleWild:
10762       /* PUSH Fabien */
10763       case WhiteHSideCastleFR:
10764       case WhiteASideCastleFR:
10765       case BlackHSideCastleFR:
10766       case BlackASideCastleFR:
10767       /* POP Fabien */
10768         if (appData.debugMode)
10769           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10770         fromX = currentMoveString[0] - AAA;
10771         fromY = currentMoveString[1] - ONE;
10772         toX = currentMoveString[2] - AAA;
10773         toY = currentMoveString[3] - ONE;
10774         promoChar = currentMoveString[4];
10775         break;
10776
10777       case WhiteDrop:
10778       case BlackDrop:
10779         if (appData.debugMode)
10780           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10781         fromX = moveType == WhiteDrop ?
10782           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10783         (int) CharToPiece(ToLower(currentMoveString[0]));
10784         fromY = DROP_RANK;
10785         toX = currentMoveString[2] - AAA;
10786         toY = currentMoveString[3] - ONE;
10787         break;
10788
10789       case WhiteWins:
10790       case BlackWins:
10791       case GameIsDrawn:
10792       case GameUnfinished:
10793         if (appData.debugMode)
10794           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10795         p = strchr(yy_text, '{');
10796         if (p == NULL) p = strchr(yy_text, '(');
10797         if (p == NULL) {
10798             p = yy_text;
10799             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10800         } else {
10801             q = strchr(p, *p == '{' ? '}' : ')');
10802             if (q != NULL) *q = NULLCHAR;
10803             p++;
10804         }
10805         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10806         GameEnds(moveType, p, GE_FILE);
10807         done = TRUE;
10808         if (cmailMsgLoaded) {
10809             ClearHighlights();
10810             flipView = WhiteOnMove(currentMove);
10811             if (moveType == GameUnfinished) flipView = !flipView;
10812             if (appData.debugMode)
10813               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10814         }
10815         break;
10816
10817       case EndOfFile:
10818         if (appData.debugMode)
10819           fprintf(debugFP, "Parser hit end of file\n");
10820         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10821           case MT_NONE:
10822           case MT_CHECK:
10823             break;
10824           case MT_CHECKMATE:
10825           case MT_STAINMATE:
10826             if (WhiteOnMove(currentMove)) {
10827                 GameEnds(BlackWins, "Black mates", GE_FILE);
10828             } else {
10829                 GameEnds(WhiteWins, "White mates", GE_FILE);
10830             }
10831             break;
10832           case MT_STALEMATE:
10833             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10834             break;
10835         }
10836         done = TRUE;
10837         break;
10838
10839       case MoveNumberOne:
10840         if (lastLoadGameStart == GNUChessGame) {
10841             /* GNUChessGames have numbers, but they aren't move numbers */
10842             if (appData.debugMode)
10843               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10844                       yy_text, (int) moveType);
10845             return LoadGameOneMove(EndOfFile); /* tail recursion */
10846         }
10847         /* else fall thru */
10848
10849       case XBoardGame:
10850       case GNUChessGame:
10851       case PGNTag:
10852         /* Reached start of next game in file */
10853         if (appData.debugMode)
10854           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10855         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10856           case MT_NONE:
10857           case MT_CHECK:
10858             break;
10859           case MT_CHECKMATE:
10860           case MT_STAINMATE:
10861             if (WhiteOnMove(currentMove)) {
10862                 GameEnds(BlackWins, "Black mates", GE_FILE);
10863             } else {
10864                 GameEnds(WhiteWins, "White mates", GE_FILE);
10865             }
10866             break;
10867           case MT_STALEMATE:
10868             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10869             break;
10870         }
10871         done = TRUE;
10872         break;
10873
10874       case PositionDiagram:     /* should not happen; ignore */
10875       case ElapsedTime:         /* ignore */
10876       case NAG:                 /* ignore */
10877         if (appData.debugMode)
10878           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10879                   yy_text, (int) moveType);
10880         return LoadGameOneMove(EndOfFile); /* tail recursion */
10881
10882       case IllegalMove:
10883         if (appData.testLegality) {
10884             if (appData.debugMode)
10885               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10886             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10887                     (forwardMostMove / 2) + 1,
10888                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10889             DisplayError(move, 0);
10890             done = TRUE;
10891         } else {
10892             if (appData.debugMode)
10893               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10894                       yy_text, currentMoveString);
10895             fromX = currentMoveString[0] - AAA;
10896             fromY = currentMoveString[1] - ONE;
10897             toX = currentMoveString[2] - AAA;
10898             toY = currentMoveString[3] - ONE;
10899             promoChar = currentMoveString[4];
10900         }
10901         break;
10902
10903       case AmbiguousMove:
10904         if (appData.debugMode)
10905           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10906         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10907                 (forwardMostMove / 2) + 1,
10908                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10909         DisplayError(move, 0);
10910         done = TRUE;
10911         break;
10912
10913       default:
10914       case ImpossibleMove:
10915         if (appData.debugMode)
10916           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10917         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10918                 (forwardMostMove / 2) + 1,
10919                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10920         DisplayError(move, 0);
10921         done = TRUE;
10922         break;
10923     }
10924
10925     if (done) {
10926         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10927             DrawPosition(FALSE, boards[currentMove]);
10928             DisplayBothClocks();
10929             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10930               DisplayComment(currentMove - 1, commentList[currentMove]);
10931         }
10932         (void) StopLoadGameTimer();
10933         gameFileFP = NULL;
10934         cmailOldMove = forwardMostMove;
10935         return FALSE;
10936     } else {
10937         /* currentMoveString is set as a side-effect of yylex */
10938
10939         thinkOutput[0] = NULLCHAR;
10940         MakeMove(fromX, fromY, toX, toY, promoChar);
10941         currentMove = forwardMostMove;
10942         return TRUE;
10943     }
10944 }
10945
10946 /* Load the nth game from the given file */
10947 int
10948 LoadGameFromFile (char *filename, int n, char *title, int useList)
10949 {
10950     FILE *f;
10951     char buf[MSG_SIZ];
10952
10953     if (strcmp(filename, "-") == 0) {
10954         f = stdin;
10955         title = "stdin";
10956     } else {
10957         f = fopen(filename, "rb");
10958         if (f == NULL) {
10959           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10960             DisplayError(buf, errno);
10961             return FALSE;
10962         }
10963     }
10964     if (fseek(f, 0, 0) == -1) {
10965         /* f is not seekable; probably a pipe */
10966         useList = FALSE;
10967     }
10968     if (useList && n == 0) {
10969         int error = GameListBuild(f);
10970         if (error) {
10971             DisplayError(_("Cannot build game list"), error);
10972         } else if (!ListEmpty(&gameList) &&
10973                    ((ListGame *) gameList.tailPred)->number > 1) {
10974             GameListPopUp(f, title);
10975             return TRUE;
10976         }
10977         GameListDestroy();
10978         n = 1;
10979     }
10980     if (n == 0) n = 1;
10981     return LoadGame(f, n, title, FALSE);
10982 }
10983
10984
10985 void
10986 MakeRegisteredMove ()
10987 {
10988     int fromX, fromY, toX, toY;
10989     char promoChar;
10990     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10991         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10992           case CMAIL_MOVE:
10993           case CMAIL_DRAW:
10994             if (appData.debugMode)
10995               fprintf(debugFP, "Restoring %s for game %d\n",
10996                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10997
10998             thinkOutput[0] = NULLCHAR;
10999             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11000             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11001             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11002             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11003             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11004             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11005             MakeMove(fromX, fromY, toX, toY, promoChar);
11006             ShowMove(fromX, fromY, toX, toY);
11007
11008             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11009               case MT_NONE:
11010               case MT_CHECK:
11011                 break;
11012
11013               case MT_CHECKMATE:
11014               case MT_STAINMATE:
11015                 if (WhiteOnMove(currentMove)) {
11016                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11017                 } else {
11018                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11019                 }
11020                 break;
11021
11022               case MT_STALEMATE:
11023                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11024                 break;
11025             }
11026
11027             break;
11028
11029           case CMAIL_RESIGN:
11030             if (WhiteOnMove(currentMove)) {
11031                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11032             } else {
11033                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11034             }
11035             break;
11036
11037           case CMAIL_ACCEPT:
11038             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11039             break;
11040
11041           default:
11042             break;
11043         }
11044     }
11045
11046     return;
11047 }
11048
11049 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11050 int
11051 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11052 {
11053     int retVal;
11054
11055     if (gameNumber > nCmailGames) {
11056         DisplayError(_("No more games in this message"), 0);
11057         return FALSE;
11058     }
11059     if (f == lastLoadGameFP) {
11060         int offset = gameNumber - lastLoadGameNumber;
11061         if (offset == 0) {
11062             cmailMsg[0] = NULLCHAR;
11063             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11064                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11065                 nCmailMovesRegistered--;
11066             }
11067             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11068             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11069                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11070             }
11071         } else {
11072             if (! RegisterMove()) return FALSE;
11073         }
11074     }
11075
11076     retVal = LoadGame(f, gameNumber, title, useList);
11077
11078     /* Make move registered during previous look at this game, if any */
11079     MakeRegisteredMove();
11080
11081     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11082         commentList[currentMove]
11083           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11084         DisplayComment(currentMove - 1, commentList[currentMove]);
11085     }
11086
11087     return retVal;
11088 }
11089
11090 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11091 int
11092 ReloadGame (int offset)
11093 {
11094     int gameNumber = lastLoadGameNumber + offset;
11095     if (lastLoadGameFP == NULL) {
11096         DisplayError(_("No game has been loaded yet"), 0);
11097         return FALSE;
11098     }
11099     if (gameNumber <= 0) {
11100         DisplayError(_("Can't back up any further"), 0);
11101         return FALSE;
11102     }
11103     if (cmailMsgLoaded) {
11104         return CmailLoadGame(lastLoadGameFP, gameNumber,
11105                              lastLoadGameTitle, lastLoadGameUseList);
11106     } else {
11107         return LoadGame(lastLoadGameFP, gameNumber,
11108                         lastLoadGameTitle, lastLoadGameUseList);
11109     }
11110 }
11111
11112 int keys[EmptySquare+1];
11113
11114 int
11115 PositionMatches (Board b1, Board b2)
11116 {
11117     int r, f, sum=0;
11118     switch(appData.searchMode) {
11119         case 1: return CompareWithRights(b1, b2);
11120         case 2:
11121             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11122                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11123             }
11124             return TRUE;
11125         case 3:
11126             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11127               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11128                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11129             }
11130             return sum==0;
11131         case 4:
11132             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11133                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11134             }
11135             return sum==0;
11136     }
11137     return TRUE;
11138 }
11139
11140 #define Q_PROMO  4
11141 #define Q_EP     3
11142 #define Q_BCASTL 2
11143 #define Q_WCASTL 1
11144
11145 int pieceList[256], quickBoard[256];
11146 ChessSquare pieceType[256] = { EmptySquare };
11147 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11148 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11149 int soughtTotal, turn;
11150 Boolean epOK, flipSearch;
11151
11152 typedef struct {
11153     unsigned char piece, to;
11154 } Move;
11155
11156 #define DSIZE (250000)
11157
11158 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11159 Move *moveDatabase = initialSpace;
11160 unsigned int movePtr, dataSize = DSIZE;
11161
11162 int
11163 MakePieceList (Board board, int *counts)
11164 {
11165     int r, f, n=Q_PROMO, total=0;
11166     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11167     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11168         int sq = f + (r<<4);
11169         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11170             quickBoard[sq] = ++n;
11171             pieceList[n] = sq;
11172             pieceType[n] = board[r][f];
11173             counts[board[r][f]]++;
11174             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11175             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11176             total++;
11177         }
11178     }
11179     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11180     return total;
11181 }
11182
11183 void
11184 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11185 {
11186     int sq = fromX + (fromY<<4);
11187     int piece = quickBoard[sq];
11188     quickBoard[sq] = 0;
11189     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11190     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11191         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11192         moveDatabase[movePtr++].piece = Q_WCASTL;
11193         quickBoard[sq] = piece;
11194         piece = quickBoard[from]; quickBoard[from] = 0;
11195         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11196     } else
11197     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11198         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11199         moveDatabase[movePtr++].piece = Q_BCASTL;
11200         quickBoard[sq] = piece;
11201         piece = quickBoard[from]; quickBoard[from] = 0;
11202         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11203     } else
11204     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11205         quickBoard[(fromY<<4)+toX] = 0;
11206         moveDatabase[movePtr].piece = Q_EP;
11207         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11208         moveDatabase[movePtr].to = sq;
11209     } else
11210     if(promoPiece != pieceType[piece]) {
11211         moveDatabase[movePtr++].piece = Q_PROMO;
11212         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11213     }
11214     moveDatabase[movePtr].piece = piece;
11215     quickBoard[sq] = piece;
11216     movePtr++;
11217 }
11218
11219 int
11220 PackGame (Board board)
11221 {
11222     Move *newSpace = NULL;
11223     moveDatabase[movePtr].piece = 0; // terminate previous game
11224     if(movePtr > dataSize) {
11225         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11226         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11227         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11228         if(newSpace) {
11229             int i;
11230             Move *p = moveDatabase, *q = newSpace;
11231             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11232             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11233             moveDatabase = newSpace;
11234         } else { // calloc failed, we must be out of memory. Too bad...
11235             dataSize = 0; // prevent calloc events for all subsequent games
11236             return 0;     // and signal this one isn't cached
11237         }
11238     }
11239     movePtr++;
11240     MakePieceList(board, counts);
11241     return movePtr;
11242 }
11243
11244 int
11245 QuickCompare (Board board, int *minCounts, int *maxCounts)
11246 {   // compare according to search mode
11247     int r, f;
11248     switch(appData.searchMode)
11249     {
11250       case 1: // exact position match
11251         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11252         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11253             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11254         }
11255         break;
11256       case 2: // can have extra material on empty squares
11257         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11258             if(board[r][f] == EmptySquare) continue;
11259             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11260         }
11261         break;
11262       case 3: // material with exact Pawn structure
11263         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11264             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11265             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11266         } // fall through to material comparison
11267       case 4: // exact material
11268         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11269         break;
11270       case 6: // material range with given imbalance
11271         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11272         // fall through to range comparison
11273       case 5: // material range
11274         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11275     }
11276     return TRUE;
11277 }
11278
11279 int
11280 QuickScan (Board board, Move *move)
11281 {   // reconstruct game,and compare all positions in it
11282     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11283     do {
11284         int piece = move->piece;
11285         int to = move->to, from = pieceList[piece];
11286         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11287           if(!piece) return -1;
11288           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11289             piece = (++move)->piece;
11290             from = pieceList[piece];
11291             counts[pieceType[piece]]--;
11292             pieceType[piece] = (ChessSquare) move->to;
11293             counts[move->to]++;
11294           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11295             counts[pieceType[quickBoard[to]]]--;
11296             quickBoard[to] = 0; total--;
11297             move++;
11298             continue;
11299           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11300             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11301             from  = pieceList[piece]; // so this must be King
11302             quickBoard[from] = 0;
11303             quickBoard[to] = piece;
11304             pieceList[piece] = to;
11305             move++;
11306             continue;
11307           }
11308         }
11309         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11310         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11311         quickBoard[from] = 0;
11312         quickBoard[to] = piece;
11313         pieceList[piece] = to;
11314         cnt++; turn ^= 3;
11315         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11316            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11317            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11318                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11319           ) {
11320             static int lastCounts[EmptySquare+1];
11321             int i;
11322             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11323             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11324         } else stretch = 0;
11325         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11326         move++;
11327     } while(1);
11328 }
11329
11330 void
11331 InitSearch ()
11332 {
11333     int r, f;
11334     flipSearch = FALSE;
11335     CopyBoard(soughtBoard, boards[currentMove]);
11336     soughtTotal = MakePieceList(soughtBoard, maxSought);
11337     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11338     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11339     CopyBoard(reverseBoard, boards[currentMove]);
11340     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11341         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11342         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11343         reverseBoard[r][f] = piece;
11344     }
11345     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11346     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11347     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11348                  || (boards[currentMove][CASTLING][2] == NoRights || 
11349                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11350                  && (boards[currentMove][CASTLING][5] == NoRights || 
11351                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11352       ) {
11353         flipSearch = TRUE;
11354         CopyBoard(flipBoard, soughtBoard);
11355         CopyBoard(rotateBoard, reverseBoard);
11356         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11357             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11358             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11359         }
11360     }
11361     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11362     if(appData.searchMode >= 5) {
11363         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11364         MakePieceList(soughtBoard, minSought);
11365         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11366     }
11367     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11368         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11369 }
11370
11371 GameInfo dummyInfo;
11372
11373 int
11374 GameContainsPosition (FILE *f, ListGame *lg)
11375 {
11376     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11377     int fromX, fromY, toX, toY;
11378     char promoChar;
11379     static int initDone=FALSE;
11380
11381     // weed out games based on numerical tag comparison
11382     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11383     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11384     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11385     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11386     if(!initDone) {
11387         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11388         initDone = TRUE;
11389     }
11390     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11391     else CopyBoard(boards[scratch], initialPosition); // default start position
11392     if(lg->moves) {
11393         turn = btm + 1;
11394         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11395         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11396     }
11397     if(btm) plyNr++;
11398     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11399     fseek(f, lg->offset, 0);
11400     yynewfile(f);
11401     while(1) {
11402         yyboardindex = scratch;
11403         quickFlag = plyNr+1;
11404         next = Myylex();
11405         quickFlag = 0;
11406         switch(next) {
11407             case PGNTag:
11408                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11409             default:
11410                 continue;
11411
11412             case XBoardGame:
11413             case GNUChessGame:
11414                 if(plyNr) return -1; // after we have seen moves, this is for new game
11415               continue;
11416
11417             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11418             case ImpossibleMove:
11419             case WhiteWins: // game ends here with these four
11420             case BlackWins:
11421             case GameIsDrawn:
11422             case GameUnfinished:
11423                 return -1;
11424
11425             case IllegalMove:
11426                 if(appData.testLegality) return -1;
11427             case WhiteCapturesEnPassant:
11428             case BlackCapturesEnPassant:
11429             case WhitePromotion:
11430             case BlackPromotion:
11431             case WhiteNonPromotion:
11432             case BlackNonPromotion:
11433             case NormalMove:
11434             case WhiteKingSideCastle:
11435             case WhiteQueenSideCastle:
11436             case BlackKingSideCastle:
11437             case BlackQueenSideCastle:
11438             case WhiteKingSideCastleWild:
11439             case WhiteQueenSideCastleWild:
11440             case BlackKingSideCastleWild:
11441             case BlackQueenSideCastleWild:
11442             case WhiteHSideCastleFR:
11443             case WhiteASideCastleFR:
11444             case BlackHSideCastleFR:
11445             case BlackASideCastleFR:
11446                 fromX = currentMoveString[0] - AAA;
11447                 fromY = currentMoveString[1] - ONE;
11448                 toX = currentMoveString[2] - AAA;
11449                 toY = currentMoveString[3] - ONE;
11450                 promoChar = currentMoveString[4];
11451                 break;
11452             case WhiteDrop:
11453             case BlackDrop:
11454                 fromX = next == WhiteDrop ?
11455                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11456                   (int) CharToPiece(ToLower(currentMoveString[0]));
11457                 fromY = DROP_RANK;
11458                 toX = currentMoveString[2] - AAA;
11459                 toY = currentMoveString[3] - ONE;
11460                 promoChar = 0;
11461                 break;
11462         }
11463         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11464         plyNr++;
11465         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11466         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11467         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11468         if(appData.findMirror) {
11469             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11470             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11471         }
11472     }
11473 }
11474
11475 /* Load the nth game from open file f */
11476 int
11477 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11478 {
11479     ChessMove cm;
11480     char buf[MSG_SIZ];
11481     int gn = gameNumber;
11482     ListGame *lg = NULL;
11483     int numPGNTags = 0;
11484     int err, pos = -1;
11485     GameMode oldGameMode;
11486     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11487
11488     if (appData.debugMode)
11489         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11490
11491     if (gameMode == Training )
11492         SetTrainingModeOff();
11493
11494     oldGameMode = gameMode;
11495     if (gameMode != BeginningOfGame) {
11496       Reset(FALSE, TRUE);
11497     }
11498
11499     gameFileFP = f;
11500     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11501         fclose(lastLoadGameFP);
11502     }
11503
11504     if (useList) {
11505         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11506
11507         if (lg) {
11508             fseek(f, lg->offset, 0);
11509             GameListHighlight(gameNumber);
11510             pos = lg->position;
11511             gn = 1;
11512         }
11513         else {
11514             DisplayError(_("Game number out of range"), 0);
11515             return FALSE;
11516         }
11517     } else {
11518         GameListDestroy();
11519         if (fseek(f, 0, 0) == -1) {
11520             if (f == lastLoadGameFP ?
11521                 gameNumber == lastLoadGameNumber + 1 :
11522                 gameNumber == 1) {
11523                 gn = 1;
11524             } else {
11525                 DisplayError(_("Can't seek on game file"), 0);
11526                 return FALSE;
11527             }
11528         }
11529     }
11530     lastLoadGameFP = f;
11531     lastLoadGameNumber = gameNumber;
11532     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11533     lastLoadGameUseList = useList;
11534
11535     yynewfile(f);
11536
11537     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11538       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11539                 lg->gameInfo.black);
11540             DisplayTitle(buf);
11541     } else if (*title != NULLCHAR) {
11542         if (gameNumber > 1) {
11543           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11544             DisplayTitle(buf);
11545         } else {
11546             DisplayTitle(title);
11547         }
11548     }
11549
11550     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11551         gameMode = PlayFromGameFile;
11552         ModeHighlight();
11553     }
11554
11555     currentMove = forwardMostMove = backwardMostMove = 0;
11556     CopyBoard(boards[0], initialPosition);
11557     StopClocks();
11558
11559     /*
11560      * Skip the first gn-1 games in the file.
11561      * Also skip over anything that precedes an identifiable
11562      * start of game marker, to avoid being confused by
11563      * garbage at the start of the file.  Currently
11564      * recognized start of game markers are the move number "1",
11565      * the pattern "gnuchess .* game", the pattern
11566      * "^[#;%] [^ ]* game file", and a PGN tag block.
11567      * A game that starts with one of the latter two patterns
11568      * will also have a move number 1, possibly
11569      * following a position diagram.
11570      * 5-4-02: Let's try being more lenient and allowing a game to
11571      * start with an unnumbered move.  Does that break anything?
11572      */
11573     cm = lastLoadGameStart = EndOfFile;
11574     while (gn > 0) {
11575         yyboardindex = forwardMostMove;
11576         cm = (ChessMove) Myylex();
11577         switch (cm) {
11578           case EndOfFile:
11579             if (cmailMsgLoaded) {
11580                 nCmailGames = CMAIL_MAX_GAMES - gn;
11581             } else {
11582                 Reset(TRUE, TRUE);
11583                 DisplayError(_("Game not found in file"), 0);
11584             }
11585             return FALSE;
11586
11587           case GNUChessGame:
11588           case XBoardGame:
11589             gn--;
11590             lastLoadGameStart = cm;
11591             break;
11592
11593           case MoveNumberOne:
11594             switch (lastLoadGameStart) {
11595               case GNUChessGame:
11596               case XBoardGame:
11597               case PGNTag:
11598                 break;
11599               case MoveNumberOne:
11600               case EndOfFile:
11601                 gn--;           /* count this game */
11602                 lastLoadGameStart = cm;
11603                 break;
11604               default:
11605                 /* impossible */
11606                 break;
11607             }
11608             break;
11609
11610           case PGNTag:
11611             switch (lastLoadGameStart) {
11612               case GNUChessGame:
11613               case PGNTag:
11614               case MoveNumberOne:
11615               case EndOfFile:
11616                 gn--;           /* count this game */
11617                 lastLoadGameStart = cm;
11618                 break;
11619               case XBoardGame:
11620                 lastLoadGameStart = cm; /* game counted already */
11621                 break;
11622               default:
11623                 /* impossible */
11624                 break;
11625             }
11626             if (gn > 0) {
11627                 do {
11628                     yyboardindex = forwardMostMove;
11629                     cm = (ChessMove) Myylex();
11630                 } while (cm == PGNTag || cm == Comment);
11631             }
11632             break;
11633
11634           case WhiteWins:
11635           case BlackWins:
11636           case GameIsDrawn:
11637             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11638                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11639                     != CMAIL_OLD_RESULT) {
11640                     nCmailResults ++ ;
11641                     cmailResult[  CMAIL_MAX_GAMES
11642                                 - gn - 1] = CMAIL_OLD_RESULT;
11643                 }
11644             }
11645             break;
11646
11647           case NormalMove:
11648             /* Only a NormalMove can be at the start of a game
11649              * without a position diagram. */
11650             if (lastLoadGameStart == EndOfFile ) {
11651               gn--;
11652               lastLoadGameStart = MoveNumberOne;
11653             }
11654             break;
11655
11656           default:
11657             break;
11658         }
11659     }
11660
11661     if (appData.debugMode)
11662       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11663
11664     if (cm == XBoardGame) {
11665         /* Skip any header junk before position diagram and/or move 1 */
11666         for (;;) {
11667             yyboardindex = forwardMostMove;
11668             cm = (ChessMove) Myylex();
11669
11670             if (cm == EndOfFile ||
11671                 cm == GNUChessGame || cm == XBoardGame) {
11672                 /* Empty game; pretend end-of-file and handle later */
11673                 cm = EndOfFile;
11674                 break;
11675             }
11676
11677             if (cm == MoveNumberOne || cm == PositionDiagram ||
11678                 cm == PGNTag || cm == Comment)
11679               break;
11680         }
11681     } else if (cm == GNUChessGame) {
11682         if (gameInfo.event != NULL) {
11683             free(gameInfo.event);
11684         }
11685         gameInfo.event = StrSave(yy_text);
11686     }
11687
11688     startedFromSetupPosition = FALSE;
11689     while (cm == PGNTag) {
11690         if (appData.debugMode)
11691           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11692         err = ParsePGNTag(yy_text, &gameInfo);
11693         if (!err) numPGNTags++;
11694
11695         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11696         if(gameInfo.variant != oldVariant) {
11697             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11698             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11699             InitPosition(TRUE);
11700             oldVariant = gameInfo.variant;
11701             if (appData.debugMode)
11702               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11703         }
11704
11705
11706         if (gameInfo.fen != NULL) {
11707           Board initial_position;
11708           startedFromSetupPosition = TRUE;
11709           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11710             Reset(TRUE, TRUE);
11711             DisplayError(_("Bad FEN position in file"), 0);
11712             return FALSE;
11713           }
11714           CopyBoard(boards[0], initial_position);
11715           if (blackPlaysFirst) {
11716             currentMove = forwardMostMove = backwardMostMove = 1;
11717             CopyBoard(boards[1], initial_position);
11718             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11719             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11720             timeRemaining[0][1] = whiteTimeRemaining;
11721             timeRemaining[1][1] = blackTimeRemaining;
11722             if (commentList[0] != NULL) {
11723               commentList[1] = commentList[0];
11724               commentList[0] = NULL;
11725             }
11726           } else {
11727             currentMove = forwardMostMove = backwardMostMove = 0;
11728           }
11729           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11730           {   int i;
11731               initialRulePlies = FENrulePlies;
11732               for( i=0; i< nrCastlingRights; i++ )
11733                   initialRights[i] = initial_position[CASTLING][i];
11734           }
11735           yyboardindex = forwardMostMove;
11736           free(gameInfo.fen);
11737           gameInfo.fen = NULL;
11738         }
11739
11740         yyboardindex = forwardMostMove;
11741         cm = (ChessMove) Myylex();
11742
11743         /* Handle comments interspersed among the tags */
11744         while (cm == Comment) {
11745             char *p;
11746             if (appData.debugMode)
11747               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11748             p = yy_text;
11749             AppendComment(currentMove, p, FALSE);
11750             yyboardindex = forwardMostMove;
11751             cm = (ChessMove) Myylex();
11752         }
11753     }
11754
11755     /* don't rely on existence of Event tag since if game was
11756      * pasted from clipboard the Event tag may not exist
11757      */
11758     if (numPGNTags > 0){
11759         char *tags;
11760         if (gameInfo.variant == VariantNormal) {
11761           VariantClass v = StringToVariant(gameInfo.event);
11762           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11763           if(v < VariantShogi) gameInfo.variant = v;
11764         }
11765         if (!matchMode) {
11766           if( appData.autoDisplayTags ) {
11767             tags = PGNTags(&gameInfo);
11768             TagsPopUp(tags, CmailMsg());
11769             free(tags);
11770           }
11771         }
11772     } else {
11773         /* Make something up, but don't display it now */
11774         SetGameInfo();
11775         TagsPopDown();
11776     }
11777
11778     if (cm == PositionDiagram) {
11779         int i, j;
11780         char *p;
11781         Board initial_position;
11782
11783         if (appData.debugMode)
11784           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11785
11786         if (!startedFromSetupPosition) {
11787             p = yy_text;
11788             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11789               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11790                 switch (*p) {
11791                   case '{':
11792                   case '[':
11793                   case '-':
11794                   case ' ':
11795                   case '\t':
11796                   case '\n':
11797                   case '\r':
11798                     break;
11799                   default:
11800                     initial_position[i][j++] = CharToPiece(*p);
11801                     break;
11802                 }
11803             while (*p == ' ' || *p == '\t' ||
11804                    *p == '\n' || *p == '\r') p++;
11805
11806             if (strncmp(p, "black", strlen("black"))==0)
11807               blackPlaysFirst = TRUE;
11808             else
11809               blackPlaysFirst = FALSE;
11810             startedFromSetupPosition = TRUE;
11811
11812             CopyBoard(boards[0], initial_position);
11813             if (blackPlaysFirst) {
11814                 currentMove = forwardMostMove = backwardMostMove = 1;
11815                 CopyBoard(boards[1], initial_position);
11816                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11817                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11818                 timeRemaining[0][1] = whiteTimeRemaining;
11819                 timeRemaining[1][1] = blackTimeRemaining;
11820                 if (commentList[0] != NULL) {
11821                     commentList[1] = commentList[0];
11822                     commentList[0] = NULL;
11823                 }
11824             } else {
11825                 currentMove = forwardMostMove = backwardMostMove = 0;
11826             }
11827         }
11828         yyboardindex = forwardMostMove;
11829         cm = (ChessMove) Myylex();
11830     }
11831
11832     if (first.pr == NoProc) {
11833         StartChessProgram(&first);
11834     }
11835     InitChessProgram(&first, FALSE);
11836     SendToProgram("force\n", &first);
11837     if (startedFromSetupPosition) {
11838         SendBoard(&first, forwardMostMove);
11839     if (appData.debugMode) {
11840         fprintf(debugFP, "Load Game\n");
11841     }
11842         DisplayBothClocks();
11843     }
11844
11845     /* [HGM] server: flag to write setup moves in broadcast file as one */
11846     loadFlag = appData.suppressLoadMoves;
11847
11848     while (cm == Comment) {
11849         char *p;
11850         if (appData.debugMode)
11851           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11852         p = yy_text;
11853         AppendComment(currentMove, p, FALSE);
11854         yyboardindex = forwardMostMove;
11855         cm = (ChessMove) Myylex();
11856     }
11857
11858     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11859         cm == WhiteWins || cm == BlackWins ||
11860         cm == GameIsDrawn || cm == GameUnfinished) {
11861         DisplayMessage("", _("No moves in game"));
11862         if (cmailMsgLoaded) {
11863             if (appData.debugMode)
11864               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11865             ClearHighlights();
11866             flipView = FALSE;
11867         }
11868         DrawPosition(FALSE, boards[currentMove]);
11869         DisplayBothClocks();
11870         gameMode = EditGame;
11871         ModeHighlight();
11872         gameFileFP = NULL;
11873         cmailOldMove = 0;
11874         return TRUE;
11875     }
11876
11877     // [HGM] PV info: routine tests if comment empty
11878     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11879         DisplayComment(currentMove - 1, commentList[currentMove]);
11880     }
11881     if (!matchMode && appData.timeDelay != 0)
11882       DrawPosition(FALSE, boards[currentMove]);
11883
11884     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11885       programStats.ok_to_send = 1;
11886     }
11887
11888     /* if the first token after the PGN tags is a move
11889      * and not move number 1, retrieve it from the parser
11890      */
11891     if (cm != MoveNumberOne)
11892         LoadGameOneMove(cm);
11893
11894     /* load the remaining moves from the file */
11895     while (LoadGameOneMove(EndOfFile)) {
11896       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11897       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11898     }
11899
11900     /* rewind to the start of the game */
11901     currentMove = backwardMostMove;
11902
11903     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11904
11905     if (oldGameMode == AnalyzeFile ||
11906         oldGameMode == AnalyzeMode) {
11907       AnalyzeFileEvent();
11908     }
11909
11910     if (!matchMode && pos >= 0) {
11911         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11912     } else
11913     if (matchMode || appData.timeDelay == 0) {
11914       ToEndEvent();
11915     } else if (appData.timeDelay > 0) {
11916       AutoPlayGameLoop();
11917     }
11918
11919     if (appData.debugMode)
11920         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11921
11922     loadFlag = 0; /* [HGM] true game starts */
11923     return TRUE;
11924 }
11925
11926 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11927 int
11928 ReloadPosition (int offset)
11929 {
11930     int positionNumber = lastLoadPositionNumber + offset;
11931     if (lastLoadPositionFP == NULL) {
11932         DisplayError(_("No position has been loaded yet"), 0);
11933         return FALSE;
11934     }
11935     if (positionNumber <= 0) {
11936         DisplayError(_("Can't back up any further"), 0);
11937         return FALSE;
11938     }
11939     return LoadPosition(lastLoadPositionFP, positionNumber,
11940                         lastLoadPositionTitle);
11941 }
11942
11943 /* Load the nth position from the given file */
11944 int
11945 LoadPositionFromFile (char *filename, int n, char *title)
11946 {
11947     FILE *f;
11948     char buf[MSG_SIZ];
11949
11950     if (strcmp(filename, "-") == 0) {
11951         return LoadPosition(stdin, n, "stdin");
11952     } else {
11953         f = fopen(filename, "rb");
11954         if (f == NULL) {
11955             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11956             DisplayError(buf, errno);
11957             return FALSE;
11958         } else {
11959             return LoadPosition(f, n, title);
11960         }
11961     }
11962 }
11963
11964 /* Load the nth position from the given open file, and close it */
11965 int
11966 LoadPosition (FILE *f, int positionNumber, char *title)
11967 {
11968     char *p, line[MSG_SIZ];
11969     Board initial_position;
11970     int i, j, fenMode, pn;
11971
11972     if (gameMode == Training )
11973         SetTrainingModeOff();
11974
11975     if (gameMode != BeginningOfGame) {
11976         Reset(FALSE, TRUE);
11977     }
11978     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11979         fclose(lastLoadPositionFP);
11980     }
11981     if (positionNumber == 0) positionNumber = 1;
11982     lastLoadPositionFP = f;
11983     lastLoadPositionNumber = positionNumber;
11984     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11985     if (first.pr == NoProc && !appData.noChessProgram) {
11986       StartChessProgram(&first);
11987       InitChessProgram(&first, FALSE);
11988     }
11989     pn = positionNumber;
11990     if (positionNumber < 0) {
11991         /* Negative position number means to seek to that byte offset */
11992         if (fseek(f, -positionNumber, 0) == -1) {
11993             DisplayError(_("Can't seek on position file"), 0);
11994             return FALSE;
11995         };
11996         pn = 1;
11997     } else {
11998         if (fseek(f, 0, 0) == -1) {
11999             if (f == lastLoadPositionFP ?
12000                 positionNumber == lastLoadPositionNumber + 1 :
12001                 positionNumber == 1) {
12002                 pn = 1;
12003             } else {
12004                 DisplayError(_("Can't seek on position file"), 0);
12005                 return FALSE;
12006             }
12007         }
12008     }
12009     /* See if this file is FEN or old-style xboard */
12010     if (fgets(line, MSG_SIZ, f) == NULL) {
12011         DisplayError(_("Position not found in file"), 0);
12012         return FALSE;
12013     }
12014     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12015     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12016
12017     if (pn >= 2) {
12018         if (fenMode || line[0] == '#') pn--;
12019         while (pn > 0) {
12020             /* skip positions before number pn */
12021             if (fgets(line, MSG_SIZ, f) == NULL) {
12022                 Reset(TRUE, TRUE);
12023                 DisplayError(_("Position not found in file"), 0);
12024                 return FALSE;
12025             }
12026             if (fenMode || line[0] == '#') pn--;
12027         }
12028     }
12029
12030     if (fenMode) {
12031         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12032             DisplayError(_("Bad FEN position in file"), 0);
12033             return FALSE;
12034         }
12035     } else {
12036         (void) fgets(line, MSG_SIZ, f);
12037         (void) fgets(line, MSG_SIZ, f);
12038
12039         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12040             (void) fgets(line, MSG_SIZ, f);
12041             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12042                 if (*p == ' ')
12043                   continue;
12044                 initial_position[i][j++] = CharToPiece(*p);
12045             }
12046         }
12047
12048         blackPlaysFirst = FALSE;
12049         if (!feof(f)) {
12050             (void) fgets(line, MSG_SIZ, f);
12051             if (strncmp(line, "black", strlen("black"))==0)
12052               blackPlaysFirst = TRUE;
12053         }
12054     }
12055     startedFromSetupPosition = TRUE;
12056
12057     CopyBoard(boards[0], initial_position);
12058     if (blackPlaysFirst) {
12059         currentMove = forwardMostMove = backwardMostMove = 1;
12060         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12061         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12062         CopyBoard(boards[1], initial_position);
12063         DisplayMessage("", _("Black to play"));
12064     } else {
12065         currentMove = forwardMostMove = backwardMostMove = 0;
12066         DisplayMessage("", _("White to play"));
12067     }
12068     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12069     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12070         SendToProgram("force\n", &first);
12071         SendBoard(&first, forwardMostMove);
12072     }
12073     if (appData.debugMode) {
12074 int i, j;
12075   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12076   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12077         fprintf(debugFP, "Load Position\n");
12078     }
12079
12080     if (positionNumber > 1) {
12081       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12082         DisplayTitle(line);
12083     } else {
12084         DisplayTitle(title);
12085     }
12086     gameMode = EditGame;
12087     ModeHighlight();
12088     ResetClocks();
12089     timeRemaining[0][1] = whiteTimeRemaining;
12090     timeRemaining[1][1] = blackTimeRemaining;
12091     DrawPosition(FALSE, boards[currentMove]);
12092
12093     return TRUE;
12094 }
12095
12096
12097 void
12098 CopyPlayerNameIntoFileName (char **dest, char *src)
12099 {
12100     while (*src != NULLCHAR && *src != ',') {
12101         if (*src == ' ') {
12102             *(*dest)++ = '_';
12103             src++;
12104         } else {
12105             *(*dest)++ = *src++;
12106         }
12107     }
12108 }
12109
12110 char *
12111 DefaultFileName (char *ext)
12112 {
12113     static char def[MSG_SIZ];
12114     char *p;
12115
12116     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12117         p = def;
12118         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12119         *p++ = '-';
12120         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12121         *p++ = '.';
12122         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12123     } else {
12124         def[0] = NULLCHAR;
12125     }
12126     return def;
12127 }
12128
12129 /* Save the current game to the given file */
12130 int
12131 SaveGameToFile (char *filename, int append)
12132 {
12133     FILE *f;
12134     char buf[MSG_SIZ];
12135     int result, i, t,tot=0;
12136
12137     if (strcmp(filename, "-") == 0) {
12138         return SaveGame(stdout, 0, NULL);
12139     } else {
12140         for(i=0; i<10; i++) { // upto 10 tries
12141              f = fopen(filename, append ? "a" : "w");
12142              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12143              if(f || errno != 13) break;
12144              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12145              tot += t;
12146         }
12147         if (f == NULL) {
12148             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12149             DisplayError(buf, errno);
12150             return FALSE;
12151         } else {
12152             safeStrCpy(buf, lastMsg, MSG_SIZ);
12153             DisplayMessage(_("Waiting for access to save file"), "");
12154             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12155             DisplayMessage(_("Saving game"), "");
12156             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12157             result = SaveGame(f, 0, NULL);
12158             DisplayMessage(buf, "");
12159             return result;
12160         }
12161     }
12162 }
12163
12164 char *
12165 SavePart (char *str)
12166 {
12167     static char buf[MSG_SIZ];
12168     char *p;
12169
12170     p = strchr(str, ' ');
12171     if (p == NULL) return str;
12172     strncpy(buf, str, p - str);
12173     buf[p - str] = NULLCHAR;
12174     return buf;
12175 }
12176
12177 #define PGN_MAX_LINE 75
12178
12179 #define PGN_SIDE_WHITE  0
12180 #define PGN_SIDE_BLACK  1
12181
12182 static int
12183 FindFirstMoveOutOfBook (int side)
12184 {
12185     int result = -1;
12186
12187     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12188         int index = backwardMostMove;
12189         int has_book_hit = 0;
12190
12191         if( (index % 2) != side ) {
12192             index++;
12193         }
12194
12195         while( index < forwardMostMove ) {
12196             /* Check to see if engine is in book */
12197             int depth = pvInfoList[index].depth;
12198             int score = pvInfoList[index].score;
12199             int in_book = 0;
12200
12201             if( depth <= 2 ) {
12202                 in_book = 1;
12203             }
12204             else if( score == 0 && depth == 63 ) {
12205                 in_book = 1; /* Zappa */
12206             }
12207             else if( score == 2 && depth == 99 ) {
12208                 in_book = 1; /* Abrok */
12209             }
12210
12211             has_book_hit += in_book;
12212
12213             if( ! in_book ) {
12214                 result = index;
12215
12216                 break;
12217             }
12218
12219             index += 2;
12220         }
12221     }
12222
12223     return result;
12224 }
12225
12226 void
12227 GetOutOfBookInfo (char * buf)
12228 {
12229     int oob[2];
12230     int i;
12231     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12232
12233     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12234     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12235
12236     *buf = '\0';
12237
12238     if( oob[0] >= 0 || oob[1] >= 0 ) {
12239         for( i=0; i<2; i++ ) {
12240             int idx = oob[i];
12241
12242             if( idx >= 0 ) {
12243                 if( i > 0 && oob[0] >= 0 ) {
12244                     strcat( buf, "   " );
12245                 }
12246
12247                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12248                 sprintf( buf+strlen(buf), "%s%.2f",
12249                     pvInfoList[idx].score >= 0 ? "+" : "",
12250                     pvInfoList[idx].score / 100.0 );
12251             }
12252         }
12253     }
12254 }
12255
12256 /* Save game in PGN style and close the file */
12257 int
12258 SaveGamePGN (FILE *f)
12259 {
12260     int i, offset, linelen, newblock;
12261     time_t tm;
12262 //    char *movetext;
12263     char numtext[32];
12264     int movelen, numlen, blank;
12265     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12266
12267     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12268
12269     tm = time((time_t *) NULL);
12270
12271     PrintPGNTags(f, &gameInfo);
12272
12273     if (backwardMostMove > 0 || startedFromSetupPosition) {
12274         char *fen = PositionToFEN(backwardMostMove, NULL);
12275         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12276         fprintf(f, "\n{--------------\n");
12277         PrintPosition(f, backwardMostMove);
12278         fprintf(f, "--------------}\n");
12279         free(fen);
12280     }
12281     else {
12282         /* [AS] Out of book annotation */
12283         if( appData.saveOutOfBookInfo ) {
12284             char buf[64];
12285
12286             GetOutOfBookInfo( buf );
12287
12288             if( buf[0] != '\0' ) {
12289                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12290             }
12291         }
12292
12293         fprintf(f, "\n");
12294     }
12295
12296     i = backwardMostMove;
12297     linelen = 0;
12298     newblock = TRUE;
12299
12300     while (i < forwardMostMove) {
12301         /* Print comments preceding this move */
12302         if (commentList[i] != NULL) {
12303             if (linelen > 0) fprintf(f, "\n");
12304             fprintf(f, "%s", commentList[i]);
12305             linelen = 0;
12306             newblock = TRUE;
12307         }
12308
12309         /* Format move number */
12310         if ((i % 2) == 0)
12311           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12312         else
12313           if (newblock)
12314             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12315           else
12316             numtext[0] = NULLCHAR;
12317
12318         numlen = strlen(numtext);
12319         newblock = FALSE;
12320
12321         /* Print move number */
12322         blank = linelen > 0 && numlen > 0;
12323         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12324             fprintf(f, "\n");
12325             linelen = 0;
12326             blank = 0;
12327         }
12328         if (blank) {
12329             fprintf(f, " ");
12330             linelen++;
12331         }
12332         fprintf(f, "%s", numtext);
12333         linelen += numlen;
12334
12335         /* Get move */
12336         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12337         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12338
12339         /* Print move */
12340         blank = linelen > 0 && movelen > 0;
12341         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12342             fprintf(f, "\n");
12343             linelen = 0;
12344             blank = 0;
12345         }
12346         if (blank) {
12347             fprintf(f, " ");
12348             linelen++;
12349         }
12350         fprintf(f, "%s", move_buffer);
12351         linelen += movelen;
12352
12353         /* [AS] Add PV info if present */
12354         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12355             /* [HGM] add time */
12356             char buf[MSG_SIZ]; int seconds;
12357
12358             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12359
12360             if( seconds <= 0)
12361               buf[0] = 0;
12362             else
12363               if( seconds < 30 )
12364                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12365               else
12366                 {
12367                   seconds = (seconds + 4)/10; // round to full seconds
12368                   if( seconds < 60 )
12369                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12370                   else
12371                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12372                 }
12373
12374             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12375                       pvInfoList[i].score >= 0 ? "+" : "",
12376                       pvInfoList[i].score / 100.0,
12377                       pvInfoList[i].depth,
12378                       buf );
12379
12380             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12381
12382             /* Print score/depth */
12383             blank = linelen > 0 && movelen > 0;
12384             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12385                 fprintf(f, "\n");
12386                 linelen = 0;
12387                 blank = 0;
12388             }
12389             if (blank) {
12390                 fprintf(f, " ");
12391                 linelen++;
12392             }
12393             fprintf(f, "%s", move_buffer);
12394             linelen += movelen;
12395         }
12396
12397         i++;
12398     }
12399
12400     /* Start a new line */
12401     if (linelen > 0) fprintf(f, "\n");
12402
12403     /* Print comments after last move */
12404     if (commentList[i] != NULL) {
12405         fprintf(f, "%s\n", commentList[i]);
12406     }
12407
12408     /* Print result */
12409     if (gameInfo.resultDetails != NULL &&
12410         gameInfo.resultDetails[0] != NULLCHAR) {
12411         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12412                 PGNResult(gameInfo.result));
12413     } else {
12414         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12415     }
12416
12417     fclose(f);
12418     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12419     return TRUE;
12420 }
12421
12422 /* Save game in old style and close the file */
12423 int
12424 SaveGameOldStyle (FILE *f)
12425 {
12426     int i, offset;
12427     time_t tm;
12428
12429     tm = time((time_t *) NULL);
12430
12431     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12432     PrintOpponents(f);
12433
12434     if (backwardMostMove > 0 || startedFromSetupPosition) {
12435         fprintf(f, "\n[--------------\n");
12436         PrintPosition(f, backwardMostMove);
12437         fprintf(f, "--------------]\n");
12438     } else {
12439         fprintf(f, "\n");
12440     }
12441
12442     i = backwardMostMove;
12443     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12444
12445     while (i < forwardMostMove) {
12446         if (commentList[i] != NULL) {
12447             fprintf(f, "[%s]\n", commentList[i]);
12448         }
12449
12450         if ((i % 2) == 1) {
12451             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12452             i++;
12453         } else {
12454             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12455             i++;
12456             if (commentList[i] != NULL) {
12457                 fprintf(f, "\n");
12458                 continue;
12459             }
12460             if (i >= forwardMostMove) {
12461                 fprintf(f, "\n");
12462                 break;
12463             }
12464             fprintf(f, "%s\n", parseList[i]);
12465             i++;
12466         }
12467     }
12468
12469     if (commentList[i] != NULL) {
12470         fprintf(f, "[%s]\n", commentList[i]);
12471     }
12472
12473     /* This isn't really the old style, but it's close enough */
12474     if (gameInfo.resultDetails != NULL &&
12475         gameInfo.resultDetails[0] != NULLCHAR) {
12476         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12477                 gameInfo.resultDetails);
12478     } else {
12479         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12480     }
12481
12482     fclose(f);
12483     return TRUE;
12484 }
12485
12486 /* Save the current game to open file f and close the file */
12487 int
12488 SaveGame (FILE *f, int dummy, char *dummy2)
12489 {
12490     if (gameMode == EditPosition) EditPositionDone(TRUE);
12491     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12492     if (appData.oldSaveStyle)
12493       return SaveGameOldStyle(f);
12494     else
12495       return SaveGamePGN(f);
12496 }
12497
12498 /* Save the current position to the given file */
12499 int
12500 SavePositionToFile (char *filename)
12501 {
12502     FILE *f;
12503     char buf[MSG_SIZ];
12504
12505     if (strcmp(filename, "-") == 0) {
12506         return SavePosition(stdout, 0, NULL);
12507     } else {
12508         f = fopen(filename, "a");
12509         if (f == NULL) {
12510             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12511             DisplayError(buf, errno);
12512             return FALSE;
12513         } else {
12514             safeStrCpy(buf, lastMsg, MSG_SIZ);
12515             DisplayMessage(_("Waiting for access to save file"), "");
12516             flock(fileno(f), LOCK_EX); // [HGM] lock
12517             DisplayMessage(_("Saving position"), "");
12518             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12519             SavePosition(f, 0, NULL);
12520             DisplayMessage(buf, "");
12521             return TRUE;
12522         }
12523     }
12524 }
12525
12526 /* Save the current position to the given open file and close the file */
12527 int
12528 SavePosition (FILE *f, int dummy, char *dummy2)
12529 {
12530     time_t tm;
12531     char *fen;
12532
12533     if (gameMode == EditPosition) EditPositionDone(TRUE);
12534     if (appData.oldSaveStyle) {
12535         tm = time((time_t *) NULL);
12536
12537         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12538         PrintOpponents(f);
12539         fprintf(f, "[--------------\n");
12540         PrintPosition(f, currentMove);
12541         fprintf(f, "--------------]\n");
12542     } else {
12543         fen = PositionToFEN(currentMove, NULL);
12544         fprintf(f, "%s\n", fen);
12545         free(fen);
12546     }
12547     fclose(f);
12548     return TRUE;
12549 }
12550
12551 void
12552 ReloadCmailMsgEvent (int unregister)
12553 {
12554 #if !WIN32
12555     static char *inFilename = NULL;
12556     static char *outFilename;
12557     int i;
12558     struct stat inbuf, outbuf;
12559     int status;
12560
12561     /* Any registered moves are unregistered if unregister is set, */
12562     /* i.e. invoked by the signal handler */
12563     if (unregister) {
12564         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12565             cmailMoveRegistered[i] = FALSE;
12566             if (cmailCommentList[i] != NULL) {
12567                 free(cmailCommentList[i]);
12568                 cmailCommentList[i] = NULL;
12569             }
12570         }
12571         nCmailMovesRegistered = 0;
12572     }
12573
12574     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12575         cmailResult[i] = CMAIL_NOT_RESULT;
12576     }
12577     nCmailResults = 0;
12578
12579     if (inFilename == NULL) {
12580         /* Because the filenames are static they only get malloced once  */
12581         /* and they never get freed                                      */
12582         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12583         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12584
12585         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12586         sprintf(outFilename, "%s.out", appData.cmailGameName);
12587     }
12588
12589     status = stat(outFilename, &outbuf);
12590     if (status < 0) {
12591         cmailMailedMove = FALSE;
12592     } else {
12593         status = stat(inFilename, &inbuf);
12594         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12595     }
12596
12597     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12598        counts the games, notes how each one terminated, etc.
12599
12600        It would be nice to remove this kludge and instead gather all
12601        the information while building the game list.  (And to keep it
12602        in the game list nodes instead of having a bunch of fixed-size
12603        parallel arrays.)  Note this will require getting each game's
12604        termination from the PGN tags, as the game list builder does
12605        not process the game moves.  --mann
12606        */
12607     cmailMsgLoaded = TRUE;
12608     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12609
12610     /* Load first game in the file or popup game menu */
12611     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12612
12613 #endif /* !WIN32 */
12614     return;
12615 }
12616
12617 int
12618 RegisterMove ()
12619 {
12620     FILE *f;
12621     char string[MSG_SIZ];
12622
12623     if (   cmailMailedMove
12624         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12625         return TRUE;            /* Allow free viewing  */
12626     }
12627
12628     /* Unregister move to ensure that we don't leave RegisterMove        */
12629     /* with the move registered when the conditions for registering no   */
12630     /* longer hold                                                       */
12631     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12632         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12633         nCmailMovesRegistered --;
12634
12635         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12636           {
12637               free(cmailCommentList[lastLoadGameNumber - 1]);
12638               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12639           }
12640     }
12641
12642     if (cmailOldMove == -1) {
12643         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12644         return FALSE;
12645     }
12646
12647     if (currentMove > cmailOldMove + 1) {
12648         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12649         return FALSE;
12650     }
12651
12652     if (currentMove < cmailOldMove) {
12653         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12654         return FALSE;
12655     }
12656
12657     if (forwardMostMove > currentMove) {
12658         /* Silently truncate extra moves */
12659         TruncateGame();
12660     }
12661
12662     if (   (currentMove == cmailOldMove + 1)
12663         || (   (currentMove == cmailOldMove)
12664             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12665                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12666         if (gameInfo.result != GameUnfinished) {
12667             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12668         }
12669
12670         if (commentList[currentMove] != NULL) {
12671             cmailCommentList[lastLoadGameNumber - 1]
12672               = StrSave(commentList[currentMove]);
12673         }
12674         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12675
12676         if (appData.debugMode)
12677           fprintf(debugFP, "Saving %s for game %d\n",
12678                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12679
12680         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12681
12682         f = fopen(string, "w");
12683         if (appData.oldSaveStyle) {
12684             SaveGameOldStyle(f); /* also closes the file */
12685
12686             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12687             f = fopen(string, "w");
12688             SavePosition(f, 0, NULL); /* also closes the file */
12689         } else {
12690             fprintf(f, "{--------------\n");
12691             PrintPosition(f, currentMove);
12692             fprintf(f, "--------------}\n\n");
12693
12694             SaveGame(f, 0, NULL); /* also closes the file*/
12695         }
12696
12697         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12698         nCmailMovesRegistered ++;
12699     } else if (nCmailGames == 1) {
12700         DisplayError(_("You have not made a move yet"), 0);
12701         return FALSE;
12702     }
12703
12704     return TRUE;
12705 }
12706
12707 void
12708 MailMoveEvent ()
12709 {
12710 #if !WIN32
12711     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12712     FILE *commandOutput;
12713     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12714     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12715     int nBuffers;
12716     int i;
12717     int archived;
12718     char *arcDir;
12719
12720     if (! cmailMsgLoaded) {
12721         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12722         return;
12723     }
12724
12725     if (nCmailGames == nCmailResults) {
12726         DisplayError(_("No unfinished games"), 0);
12727         return;
12728     }
12729
12730 #if CMAIL_PROHIBIT_REMAIL
12731     if (cmailMailedMove) {
12732       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);
12733         DisplayError(msg, 0);
12734         return;
12735     }
12736 #endif
12737
12738     if (! (cmailMailedMove || RegisterMove())) return;
12739
12740     if (   cmailMailedMove
12741         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12742       snprintf(string, MSG_SIZ, partCommandString,
12743                appData.debugMode ? " -v" : "", appData.cmailGameName);
12744         commandOutput = popen(string, "r");
12745
12746         if (commandOutput == NULL) {
12747             DisplayError(_("Failed to invoke cmail"), 0);
12748         } else {
12749             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12750                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12751             }
12752             if (nBuffers > 1) {
12753                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12754                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12755                 nBytes = MSG_SIZ - 1;
12756             } else {
12757                 (void) memcpy(msg, buffer, nBytes);
12758             }
12759             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12760
12761             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12762                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12763
12764                 archived = TRUE;
12765                 for (i = 0; i < nCmailGames; i ++) {
12766                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12767                         archived = FALSE;
12768                     }
12769                 }
12770                 if (   archived
12771                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12772                         != NULL)) {
12773                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12774                            arcDir,
12775                            appData.cmailGameName,
12776                            gameInfo.date);
12777                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12778                     cmailMsgLoaded = FALSE;
12779                 }
12780             }
12781
12782             DisplayInformation(msg);
12783             pclose(commandOutput);
12784         }
12785     } else {
12786         if ((*cmailMsg) != '\0') {
12787             DisplayInformation(cmailMsg);
12788         }
12789     }
12790
12791     return;
12792 #endif /* !WIN32 */
12793 }
12794
12795 char *
12796 CmailMsg ()
12797 {
12798 #if WIN32
12799     return NULL;
12800 #else
12801     int  prependComma = 0;
12802     char number[5];
12803     char string[MSG_SIZ];       /* Space for game-list */
12804     int  i;
12805
12806     if (!cmailMsgLoaded) return "";
12807
12808     if (cmailMailedMove) {
12809       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12810     } else {
12811         /* Create a list of games left */
12812       snprintf(string, MSG_SIZ, "[");
12813         for (i = 0; i < nCmailGames; i ++) {
12814             if (! (   cmailMoveRegistered[i]
12815                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12816                 if (prependComma) {
12817                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12818                 } else {
12819                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12820                     prependComma = 1;
12821                 }
12822
12823                 strcat(string, number);
12824             }
12825         }
12826         strcat(string, "]");
12827
12828         if (nCmailMovesRegistered + nCmailResults == 0) {
12829             switch (nCmailGames) {
12830               case 1:
12831                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12832                 break;
12833
12834               case 2:
12835                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12836                 break;
12837
12838               default:
12839                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12840                          nCmailGames);
12841                 break;
12842             }
12843         } else {
12844             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12845               case 1:
12846                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12847                          string);
12848                 break;
12849
12850               case 0:
12851                 if (nCmailResults == nCmailGames) {
12852                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12853                 } else {
12854                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12855                 }
12856                 break;
12857
12858               default:
12859                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12860                          string);
12861             }
12862         }
12863     }
12864     return cmailMsg;
12865 #endif /* WIN32 */
12866 }
12867
12868 void
12869 ResetGameEvent ()
12870 {
12871     if (gameMode == Training)
12872       SetTrainingModeOff();
12873
12874     Reset(TRUE, TRUE);
12875     cmailMsgLoaded = FALSE;
12876     if (appData.icsActive) {
12877       SendToICS(ics_prefix);
12878       SendToICS("refresh\n");
12879     }
12880 }
12881
12882 void
12883 ExitEvent (int status)
12884 {
12885     exiting++;
12886     if (exiting > 2) {
12887       /* Give up on clean exit */
12888       exit(status);
12889     }
12890     if (exiting > 1) {
12891       /* Keep trying for clean exit */
12892       return;
12893     }
12894
12895     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12896
12897     if (telnetISR != NULL) {
12898       RemoveInputSource(telnetISR);
12899     }
12900     if (icsPR != NoProc) {
12901       DestroyChildProcess(icsPR, TRUE);
12902     }
12903
12904     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12905     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12906
12907     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12908     /* make sure this other one finishes before killing it!                  */
12909     if(endingGame) { int count = 0;
12910         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12911         while(endingGame && count++ < 10) DoSleep(1);
12912         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12913     }
12914
12915     /* Kill off chess programs */
12916     if (first.pr != NoProc) {
12917         ExitAnalyzeMode();
12918
12919         DoSleep( appData.delayBeforeQuit );
12920         SendToProgram("quit\n", &first);
12921         DoSleep( appData.delayAfterQuit );
12922         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12923     }
12924     if (second.pr != NoProc) {
12925         DoSleep( appData.delayBeforeQuit );
12926         SendToProgram("quit\n", &second);
12927         DoSleep( appData.delayAfterQuit );
12928         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12929     }
12930     if (first.isr != NULL) {
12931         RemoveInputSource(first.isr);
12932     }
12933     if (second.isr != NULL) {
12934         RemoveInputSource(second.isr);
12935     }
12936
12937     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12938     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12939
12940     ShutDownFrontEnd();
12941     exit(status);
12942 }
12943
12944 void
12945 PauseEvent ()
12946 {
12947     if (appData.debugMode)
12948         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12949     if (pausing) {
12950         pausing = FALSE;
12951         ModeHighlight();
12952         if (gameMode == MachinePlaysWhite ||
12953             gameMode == MachinePlaysBlack) {
12954             StartClocks();
12955         } else {
12956             DisplayBothClocks();
12957         }
12958         if (gameMode == PlayFromGameFile) {
12959             if (appData.timeDelay >= 0)
12960                 AutoPlayGameLoop();
12961         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12962             Reset(FALSE, TRUE);
12963             SendToICS(ics_prefix);
12964             SendToICS("refresh\n");
12965         } else if (currentMove < forwardMostMove) {
12966             ForwardInner(forwardMostMove);
12967         }
12968         pauseExamInvalid = FALSE;
12969     } else {
12970         switch (gameMode) {
12971           default:
12972             return;
12973           case IcsExamining:
12974             pauseExamForwardMostMove = forwardMostMove;
12975             pauseExamInvalid = FALSE;
12976             /* fall through */
12977           case IcsObserving:
12978           case IcsPlayingWhite:
12979           case IcsPlayingBlack:
12980             pausing = TRUE;
12981             ModeHighlight();
12982             return;
12983           case PlayFromGameFile:
12984             (void) StopLoadGameTimer();
12985             pausing = TRUE;
12986             ModeHighlight();
12987             break;
12988           case BeginningOfGame:
12989             if (appData.icsActive) return;
12990             /* else fall through */
12991           case MachinePlaysWhite:
12992           case MachinePlaysBlack:
12993           case TwoMachinesPlay:
12994             if (forwardMostMove == 0)
12995               return;           /* don't pause if no one has moved */
12996             if ((gameMode == MachinePlaysWhite &&
12997                  !WhiteOnMove(forwardMostMove)) ||
12998                 (gameMode == MachinePlaysBlack &&
12999                  WhiteOnMove(forwardMostMove))) {
13000                 StopClocks();
13001             }
13002             pausing = TRUE;
13003             ModeHighlight();
13004             break;
13005         }
13006     }
13007 }
13008
13009 void
13010 EditCommentEvent ()
13011 {
13012     char title[MSG_SIZ];
13013
13014     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13015       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13016     } else {
13017       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13018                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13019                parseList[currentMove - 1]);
13020     }
13021
13022     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13023 }
13024
13025
13026 void
13027 EditTagsEvent ()
13028 {
13029     char *tags = PGNTags(&gameInfo);
13030     bookUp = FALSE;
13031     EditTagsPopUp(tags, NULL);
13032     free(tags);
13033 }
13034
13035 void
13036 AnalyzeModeEvent ()
13037 {
13038     if (appData.noChessProgram || gameMode == AnalyzeMode)
13039       return;
13040
13041     if (gameMode != AnalyzeFile) {
13042         if (!appData.icsEngineAnalyze) {
13043                EditGameEvent();
13044                if (gameMode != EditGame) return;
13045         }
13046         ResurrectChessProgram();
13047         SendToProgram("analyze\n", &first);
13048         first.analyzing = TRUE;
13049         /*first.maybeThinking = TRUE;*/
13050         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13051         EngineOutputPopUp();
13052     }
13053     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13054     pausing = FALSE;
13055     ModeHighlight();
13056     SetGameInfo();
13057
13058     StartAnalysisClock();
13059     GetTimeMark(&lastNodeCountTime);
13060     lastNodeCount = 0;
13061 }
13062
13063 void
13064 AnalyzeFileEvent ()
13065 {
13066     if (appData.noChessProgram || gameMode == AnalyzeFile)
13067       return;
13068
13069     if (gameMode != AnalyzeMode) {
13070         EditGameEvent();
13071         if (gameMode != EditGame) return;
13072         ResurrectChessProgram();
13073         SendToProgram("analyze\n", &first);
13074         first.analyzing = TRUE;
13075         /*first.maybeThinking = TRUE;*/
13076         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13077         EngineOutputPopUp();
13078     }
13079     gameMode = AnalyzeFile;
13080     pausing = FALSE;
13081     ModeHighlight();
13082     SetGameInfo();
13083
13084     StartAnalysisClock();
13085     GetTimeMark(&lastNodeCountTime);
13086     lastNodeCount = 0;
13087     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13088 }
13089
13090 void
13091 MachineWhiteEvent ()
13092 {
13093     char buf[MSG_SIZ];
13094     char *bookHit = NULL;
13095
13096     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13097       return;
13098
13099
13100     if (gameMode == PlayFromGameFile ||
13101         gameMode == TwoMachinesPlay  ||
13102         gameMode == Training         ||
13103         gameMode == AnalyzeMode      ||
13104         gameMode == EndOfGame)
13105         EditGameEvent();
13106
13107     if (gameMode == EditPosition)
13108         EditPositionDone(TRUE);
13109
13110     if (!WhiteOnMove(currentMove)) {
13111         DisplayError(_("It is not White's turn"), 0);
13112         return;
13113     }
13114
13115     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13116       ExitAnalyzeMode();
13117
13118     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13119         gameMode == AnalyzeFile)
13120         TruncateGame();
13121
13122     ResurrectChessProgram();    /* in case it isn't running */
13123     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13124         gameMode = MachinePlaysWhite;
13125         ResetClocks();
13126     } else
13127     gameMode = MachinePlaysWhite;
13128     pausing = FALSE;
13129     ModeHighlight();
13130     SetGameInfo();
13131     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13132     DisplayTitle(buf);
13133     if (first.sendName) {
13134       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13135       SendToProgram(buf, &first);
13136     }
13137     if (first.sendTime) {
13138       if (first.useColors) {
13139         SendToProgram("black\n", &first); /*gnu kludge*/
13140       }
13141       SendTimeRemaining(&first, TRUE);
13142     }
13143     if (first.useColors) {
13144       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13145     }
13146     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13147     SetMachineThinkingEnables();
13148     first.maybeThinking = TRUE;
13149     StartClocks();
13150     firstMove = FALSE;
13151
13152     if (appData.autoFlipView && !flipView) {
13153       flipView = !flipView;
13154       DrawPosition(FALSE, NULL);
13155       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13156     }
13157
13158     if(bookHit) { // [HGM] book: simulate book reply
13159         static char bookMove[MSG_SIZ]; // a bit generous?
13160
13161         programStats.nodes = programStats.depth = programStats.time =
13162         programStats.score = programStats.got_only_move = 0;
13163         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13164
13165         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13166         strcat(bookMove, bookHit);
13167         HandleMachineMove(bookMove, &first);
13168     }
13169 }
13170
13171 void
13172 MachineBlackEvent ()
13173 {
13174   char buf[MSG_SIZ];
13175   char *bookHit = NULL;
13176
13177     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13178         return;
13179
13180
13181     if (gameMode == PlayFromGameFile ||
13182         gameMode == TwoMachinesPlay  ||
13183         gameMode == Training         ||
13184         gameMode == AnalyzeMode      ||
13185         gameMode == EndOfGame)
13186         EditGameEvent();
13187
13188     if (gameMode == EditPosition)
13189         EditPositionDone(TRUE);
13190
13191     if (WhiteOnMove(currentMove)) {
13192         DisplayError(_("It is not Black's turn"), 0);
13193         return;
13194     }
13195
13196     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13197       ExitAnalyzeMode();
13198
13199     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13200         gameMode == AnalyzeFile)
13201         TruncateGame();
13202
13203     ResurrectChessProgram();    /* in case it isn't running */
13204     gameMode = MachinePlaysBlack;
13205     pausing = FALSE;
13206     ModeHighlight();
13207     SetGameInfo();
13208     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13209     DisplayTitle(buf);
13210     if (first.sendName) {
13211       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13212       SendToProgram(buf, &first);
13213     }
13214     if (first.sendTime) {
13215       if (first.useColors) {
13216         SendToProgram("white\n", &first); /*gnu kludge*/
13217       }
13218       SendTimeRemaining(&first, FALSE);
13219     }
13220     if (first.useColors) {
13221       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13222     }
13223     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13224     SetMachineThinkingEnables();
13225     first.maybeThinking = TRUE;
13226     StartClocks();
13227
13228     if (appData.autoFlipView && flipView) {
13229       flipView = !flipView;
13230       DrawPosition(FALSE, NULL);
13231       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13232     }
13233     if(bookHit) { // [HGM] book: simulate book reply
13234         static char bookMove[MSG_SIZ]; // a bit generous?
13235
13236         programStats.nodes = programStats.depth = programStats.time =
13237         programStats.score = programStats.got_only_move = 0;
13238         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13239
13240         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13241         strcat(bookMove, bookHit);
13242         HandleMachineMove(bookMove, &first);
13243     }
13244 }
13245
13246
13247 void
13248 DisplayTwoMachinesTitle ()
13249 {
13250     char buf[MSG_SIZ];
13251     if (appData.matchGames > 0) {
13252         if(appData.tourneyFile[0]) {
13253           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13254                    gameInfo.white, _("vs."), gameInfo.black,
13255                    nextGame+1, appData.matchGames+1,
13256                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13257         } else 
13258         if (first.twoMachinesColor[0] == 'w') {
13259           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13260                    gameInfo.white, _("vs."),  gameInfo.black,
13261                    first.matchWins, second.matchWins,
13262                    matchGame - 1 - (first.matchWins + second.matchWins));
13263         } else {
13264           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13265                    gameInfo.white, _("vs."), gameInfo.black,
13266                    second.matchWins, first.matchWins,
13267                    matchGame - 1 - (first.matchWins + second.matchWins));
13268         }
13269     } else {
13270       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13271     }
13272     DisplayTitle(buf);
13273 }
13274
13275 void
13276 SettingsMenuIfReady ()
13277 {
13278   if (second.lastPing != second.lastPong) {
13279     DisplayMessage("", _("Waiting for second chess program"));
13280     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13281     return;
13282   }
13283   ThawUI();
13284   DisplayMessage("", "");
13285   SettingsPopUp(&second);
13286 }
13287
13288 int
13289 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13290 {
13291     char buf[MSG_SIZ];
13292     if (cps->pr == NoProc) {
13293         StartChessProgram(cps);
13294         if (cps->protocolVersion == 1) {
13295           retry();
13296         } else {
13297           /* kludge: allow timeout for initial "feature" command */
13298           FreezeUI();
13299           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13300           DisplayMessage("", buf);
13301           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13302         }
13303         return 1;
13304     }
13305     return 0;
13306 }
13307
13308 void
13309 TwoMachinesEvent P((void))
13310 {
13311     int i;
13312     char buf[MSG_SIZ];
13313     ChessProgramState *onmove;
13314     char *bookHit = NULL;
13315     static int stalling = 0;
13316     TimeMark now;
13317     long wait;
13318
13319     if (appData.noChessProgram) return;
13320
13321     switch (gameMode) {
13322       case TwoMachinesPlay:
13323         return;
13324       case MachinePlaysWhite:
13325       case MachinePlaysBlack:
13326         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13327             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13328             return;
13329         }
13330         /* fall through */
13331       case BeginningOfGame:
13332       case PlayFromGameFile:
13333       case EndOfGame:
13334         EditGameEvent();
13335         if (gameMode != EditGame) return;
13336         break;
13337       case EditPosition:
13338         EditPositionDone(TRUE);
13339         break;
13340       case AnalyzeMode:
13341       case AnalyzeFile:
13342         ExitAnalyzeMode();
13343         break;
13344       case EditGame:
13345       default:
13346         break;
13347     }
13348
13349 //    forwardMostMove = currentMove;
13350     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13351
13352     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13353
13354     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13355     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13356       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13357       return;
13358     }
13359     if(!stalling) {
13360       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13361       SendToProgram("force\n", &second);
13362       stalling = 1;
13363       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13364       return;
13365     }
13366     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13367     if(appData.matchPause>10000 || appData.matchPause<10)
13368                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13369     wait = SubtractTimeMarks(&now, &pauseStart);
13370     if(wait < appData.matchPause) {
13371         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13372         return;
13373     }
13374     stalling = 0;
13375     DisplayMessage("", "");
13376     if (startedFromSetupPosition) {
13377         SendBoard(&second, backwardMostMove);
13378     if (appData.debugMode) {
13379         fprintf(debugFP, "Two Machines\n");
13380     }
13381     }
13382     for (i = backwardMostMove; i < forwardMostMove; i++) {
13383         SendMoveToProgram(i, &second);
13384     }
13385
13386     gameMode = TwoMachinesPlay;
13387     pausing = FALSE;
13388     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13389     SetGameInfo();
13390     DisplayTwoMachinesTitle();
13391     firstMove = TRUE;
13392     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13393         onmove = &first;
13394     } else {
13395         onmove = &second;
13396     }
13397     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13398     SendToProgram(first.computerString, &first);
13399     if (first.sendName) {
13400       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13401       SendToProgram(buf, &first);
13402     }
13403     SendToProgram(second.computerString, &second);
13404     if (second.sendName) {
13405       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13406       SendToProgram(buf, &second);
13407     }
13408
13409     ResetClocks();
13410     if (!first.sendTime || !second.sendTime) {
13411         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13412         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13413     }
13414     if (onmove->sendTime) {
13415       if (onmove->useColors) {
13416         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13417       }
13418       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13419     }
13420     if (onmove->useColors) {
13421       SendToProgram(onmove->twoMachinesColor, onmove);
13422     }
13423     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13424 //    SendToProgram("go\n", onmove);
13425     onmove->maybeThinking = TRUE;
13426     SetMachineThinkingEnables();
13427
13428     StartClocks();
13429
13430     if(bookHit) { // [HGM] book: simulate book reply
13431         static char bookMove[MSG_SIZ]; // a bit generous?
13432
13433         programStats.nodes = programStats.depth = programStats.time =
13434         programStats.score = programStats.got_only_move = 0;
13435         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13436
13437         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13438         strcat(bookMove, bookHit);
13439         savedMessage = bookMove; // args for deferred call
13440         savedState = onmove;
13441         ScheduleDelayedEvent(DeferredBookMove, 1);
13442     }
13443 }
13444
13445 void
13446 TrainingEvent ()
13447 {
13448     if (gameMode == Training) {
13449       SetTrainingModeOff();
13450       gameMode = PlayFromGameFile;
13451       DisplayMessage("", _("Training mode off"));
13452     } else {
13453       gameMode = Training;
13454       animateTraining = appData.animate;
13455
13456       /* make sure we are not already at the end of the game */
13457       if (currentMove < forwardMostMove) {
13458         SetTrainingModeOn();
13459         DisplayMessage("", _("Training mode on"));
13460       } else {
13461         gameMode = PlayFromGameFile;
13462         DisplayError(_("Already at end of game"), 0);
13463       }
13464     }
13465     ModeHighlight();
13466 }
13467
13468 void
13469 IcsClientEvent ()
13470 {
13471     if (!appData.icsActive) return;
13472     switch (gameMode) {
13473       case IcsPlayingWhite:
13474       case IcsPlayingBlack:
13475       case IcsObserving:
13476       case IcsIdle:
13477       case BeginningOfGame:
13478       case IcsExamining:
13479         return;
13480
13481       case EditGame:
13482         break;
13483
13484       case EditPosition:
13485         EditPositionDone(TRUE);
13486         break;
13487
13488       case AnalyzeMode:
13489       case AnalyzeFile:
13490         ExitAnalyzeMode();
13491         break;
13492
13493       default:
13494         EditGameEvent();
13495         break;
13496     }
13497
13498     gameMode = IcsIdle;
13499     ModeHighlight();
13500     return;
13501 }
13502
13503 void
13504 EditGameEvent ()
13505 {
13506     int i;
13507
13508     switch (gameMode) {
13509       case Training:
13510         SetTrainingModeOff();
13511         break;
13512       case MachinePlaysWhite:
13513       case MachinePlaysBlack:
13514       case BeginningOfGame:
13515         SendToProgram("force\n", &first);
13516         SetUserThinkingEnables();
13517         break;
13518       case PlayFromGameFile:
13519         (void) StopLoadGameTimer();
13520         if (gameFileFP != NULL) {
13521             gameFileFP = NULL;
13522         }
13523         break;
13524       case EditPosition:
13525         EditPositionDone(TRUE);
13526         break;
13527       case AnalyzeMode:
13528       case AnalyzeFile:
13529         ExitAnalyzeMode();
13530         SendToProgram("force\n", &first);
13531         break;
13532       case TwoMachinesPlay:
13533         GameEnds(EndOfFile, NULL, GE_PLAYER);
13534         ResurrectChessProgram();
13535         SetUserThinkingEnables();
13536         break;
13537       case EndOfGame:
13538         ResurrectChessProgram();
13539         break;
13540       case IcsPlayingBlack:
13541       case IcsPlayingWhite:
13542         DisplayError(_("Warning: You are still playing a game"), 0);
13543         break;
13544       case IcsObserving:
13545         DisplayError(_("Warning: You are still observing a game"), 0);
13546         break;
13547       case IcsExamining:
13548         DisplayError(_("Warning: You are still examining a game"), 0);
13549         break;
13550       case IcsIdle:
13551         break;
13552       case EditGame:
13553       default:
13554         return;
13555     }
13556
13557     pausing = FALSE;
13558     StopClocks();
13559     first.offeredDraw = second.offeredDraw = 0;
13560
13561     if (gameMode == PlayFromGameFile) {
13562         whiteTimeRemaining = timeRemaining[0][currentMove];
13563         blackTimeRemaining = timeRemaining[1][currentMove];
13564         DisplayTitle("");
13565     }
13566
13567     if (gameMode == MachinePlaysWhite ||
13568         gameMode == MachinePlaysBlack ||
13569         gameMode == TwoMachinesPlay ||
13570         gameMode == EndOfGame) {
13571         i = forwardMostMove;
13572         while (i > currentMove) {
13573             SendToProgram("undo\n", &first);
13574             i--;
13575         }
13576         if(!adjustedClock) {
13577         whiteTimeRemaining = timeRemaining[0][currentMove];
13578         blackTimeRemaining = timeRemaining[1][currentMove];
13579         DisplayBothClocks();
13580         }
13581         if (whiteFlag || blackFlag) {
13582             whiteFlag = blackFlag = 0;
13583         }
13584         DisplayTitle("");
13585     }
13586
13587     gameMode = EditGame;
13588     ModeHighlight();
13589     SetGameInfo();
13590 }
13591
13592
13593 void
13594 EditPositionEvent ()
13595 {
13596     if (gameMode == EditPosition) {
13597         EditGameEvent();
13598         return;
13599     }
13600
13601     EditGameEvent();
13602     if (gameMode != EditGame) return;
13603
13604     gameMode = EditPosition;
13605     ModeHighlight();
13606     SetGameInfo();
13607     if (currentMove > 0)
13608       CopyBoard(boards[0], boards[currentMove]);
13609
13610     blackPlaysFirst = !WhiteOnMove(currentMove);
13611     ResetClocks();
13612     currentMove = forwardMostMove = backwardMostMove = 0;
13613     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13614     DisplayMove(-1);
13615 }
13616
13617 void
13618 ExitAnalyzeMode ()
13619 {
13620     /* [DM] icsEngineAnalyze - possible call from other functions */
13621     if (appData.icsEngineAnalyze) {
13622         appData.icsEngineAnalyze = FALSE;
13623
13624         DisplayMessage("",_("Close ICS engine analyze..."));
13625     }
13626     if (first.analysisSupport && first.analyzing) {
13627       SendToProgram("exit\n", &first);
13628       first.analyzing = FALSE;
13629     }
13630     thinkOutput[0] = NULLCHAR;
13631 }
13632
13633 void
13634 EditPositionDone (Boolean fakeRights)
13635 {
13636     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13637
13638     startedFromSetupPosition = TRUE;
13639     InitChessProgram(&first, FALSE);
13640     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13641       boards[0][EP_STATUS] = EP_NONE;
13642       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13643     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13644         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13645         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13646       } else boards[0][CASTLING][2] = NoRights;
13647     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13648         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13649         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13650       } else boards[0][CASTLING][5] = NoRights;
13651     }
13652     SendToProgram("force\n", &first);
13653     if (blackPlaysFirst) {
13654         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13655         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13656         currentMove = forwardMostMove = backwardMostMove = 1;
13657         CopyBoard(boards[1], boards[0]);
13658     } else {
13659         currentMove = forwardMostMove = backwardMostMove = 0;
13660     }
13661     SendBoard(&first, forwardMostMove);
13662     if (appData.debugMode) {
13663         fprintf(debugFP, "EditPosDone\n");
13664     }
13665     DisplayTitle("");
13666     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13667     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13668     gameMode = EditGame;
13669     ModeHighlight();
13670     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13671     ClearHighlights(); /* [AS] */
13672 }
13673
13674 /* Pause for `ms' milliseconds */
13675 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13676 void
13677 TimeDelay (long ms)
13678 {
13679     TimeMark m1, m2;
13680
13681     GetTimeMark(&m1);
13682     do {
13683         GetTimeMark(&m2);
13684     } while (SubtractTimeMarks(&m2, &m1) < ms);
13685 }
13686
13687 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13688 void
13689 SendMultiLineToICS (char *buf)
13690 {
13691     char temp[MSG_SIZ+1], *p;
13692     int len;
13693
13694     len = strlen(buf);
13695     if (len > MSG_SIZ)
13696       len = MSG_SIZ;
13697
13698     strncpy(temp, buf, len);
13699     temp[len] = 0;
13700
13701     p = temp;
13702     while (*p) {
13703         if (*p == '\n' || *p == '\r')
13704           *p = ' ';
13705         ++p;
13706     }
13707
13708     strcat(temp, "\n");
13709     SendToICS(temp);
13710     SendToPlayer(temp, strlen(temp));
13711 }
13712
13713 void
13714 SetWhiteToPlayEvent ()
13715 {
13716     if (gameMode == EditPosition) {
13717         blackPlaysFirst = FALSE;
13718         DisplayBothClocks();    /* works because currentMove is 0 */
13719     } else if (gameMode == IcsExamining) {
13720         SendToICS(ics_prefix);
13721         SendToICS("tomove white\n");
13722     }
13723 }
13724
13725 void
13726 SetBlackToPlayEvent ()
13727 {
13728     if (gameMode == EditPosition) {
13729         blackPlaysFirst = TRUE;
13730         currentMove = 1;        /* kludge */
13731         DisplayBothClocks();
13732         currentMove = 0;
13733     } else if (gameMode == IcsExamining) {
13734         SendToICS(ics_prefix);
13735         SendToICS("tomove black\n");
13736     }
13737 }
13738
13739 void
13740 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13741 {
13742     char buf[MSG_SIZ];
13743     ChessSquare piece = boards[0][y][x];
13744
13745     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13746
13747     switch (selection) {
13748       case ClearBoard:
13749         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13750             SendToICS(ics_prefix);
13751             SendToICS("bsetup clear\n");
13752         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13753             SendToICS(ics_prefix);
13754             SendToICS("clearboard\n");
13755         } else {
13756             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13757                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13758                 for (y = 0; y < BOARD_HEIGHT; y++) {
13759                     if (gameMode == IcsExamining) {
13760                         if (boards[currentMove][y][x] != EmptySquare) {
13761                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13762                                     AAA + x, ONE + y);
13763                             SendToICS(buf);
13764                         }
13765                     } else {
13766                         boards[0][y][x] = p;
13767                     }
13768                 }
13769             }
13770         }
13771         if (gameMode == EditPosition) {
13772             DrawPosition(FALSE, boards[0]);
13773         }
13774         break;
13775
13776       case WhitePlay:
13777         SetWhiteToPlayEvent();
13778         break;
13779
13780       case BlackPlay:
13781         SetBlackToPlayEvent();
13782         break;
13783
13784       case EmptySquare:
13785         if (gameMode == IcsExamining) {
13786             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13787             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13788             SendToICS(buf);
13789         } else {
13790             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13791                 if(x == BOARD_LEFT-2) {
13792                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13793                     boards[0][y][1] = 0;
13794                 } else
13795                 if(x == BOARD_RGHT+1) {
13796                     if(y >= gameInfo.holdingsSize) break;
13797                     boards[0][y][BOARD_WIDTH-2] = 0;
13798                 } else break;
13799             }
13800             boards[0][y][x] = EmptySquare;
13801             DrawPosition(FALSE, boards[0]);
13802         }
13803         break;
13804
13805       case PromotePiece:
13806         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13807            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13808             selection = (ChessSquare) (PROMOTED piece);
13809         } else if(piece == EmptySquare) selection = WhiteSilver;
13810         else selection = (ChessSquare)((int)piece - 1);
13811         goto defaultlabel;
13812
13813       case DemotePiece:
13814         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13815            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13816             selection = (ChessSquare) (DEMOTED piece);
13817         } else if(piece == EmptySquare) selection = BlackSilver;
13818         else selection = (ChessSquare)((int)piece + 1);
13819         goto defaultlabel;
13820
13821       case WhiteQueen:
13822       case BlackQueen:
13823         if(gameInfo.variant == VariantShatranj ||
13824            gameInfo.variant == VariantXiangqi  ||
13825            gameInfo.variant == VariantCourier  ||
13826            gameInfo.variant == VariantMakruk     )
13827             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13828         goto defaultlabel;
13829
13830       case WhiteKing:
13831       case BlackKing:
13832         if(gameInfo.variant == VariantXiangqi)
13833             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13834         if(gameInfo.variant == VariantKnightmate)
13835             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13836       default:
13837         defaultlabel:
13838         if (gameMode == IcsExamining) {
13839             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13840             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13841                      PieceToChar(selection), AAA + x, ONE + y);
13842             SendToICS(buf);
13843         } else {
13844             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13845                 int n;
13846                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13847                     n = PieceToNumber(selection - BlackPawn);
13848                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13849                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13850                     boards[0][BOARD_HEIGHT-1-n][1]++;
13851                 } else
13852                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13853                     n = PieceToNumber(selection);
13854                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13855                     boards[0][n][BOARD_WIDTH-1] = selection;
13856                     boards[0][n][BOARD_WIDTH-2]++;
13857                 }
13858             } else
13859             boards[0][y][x] = selection;
13860             DrawPosition(TRUE, boards[0]);
13861         }
13862         break;
13863     }
13864 }
13865
13866
13867 void
13868 DropMenuEvent (ChessSquare selection, int x, int y)
13869 {
13870     ChessMove moveType;
13871
13872     switch (gameMode) {
13873       case IcsPlayingWhite:
13874       case MachinePlaysBlack:
13875         if (!WhiteOnMove(currentMove)) {
13876             DisplayMoveError(_("It is Black's turn"));
13877             return;
13878         }
13879         moveType = WhiteDrop;
13880         break;
13881       case IcsPlayingBlack:
13882       case MachinePlaysWhite:
13883         if (WhiteOnMove(currentMove)) {
13884             DisplayMoveError(_("It is White's turn"));
13885             return;
13886         }
13887         moveType = BlackDrop;
13888         break;
13889       case EditGame:
13890         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13891         break;
13892       default:
13893         return;
13894     }
13895
13896     if (moveType == BlackDrop && selection < BlackPawn) {
13897       selection = (ChessSquare) ((int) selection
13898                                  + (int) BlackPawn - (int) WhitePawn);
13899     }
13900     if (boards[currentMove][y][x] != EmptySquare) {
13901         DisplayMoveError(_("That square is occupied"));
13902         return;
13903     }
13904
13905     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13906 }
13907
13908 void
13909 AcceptEvent ()
13910 {
13911     /* Accept a pending offer of any kind from opponent */
13912
13913     if (appData.icsActive) {
13914         SendToICS(ics_prefix);
13915         SendToICS("accept\n");
13916     } else if (cmailMsgLoaded) {
13917         if (currentMove == cmailOldMove &&
13918             commentList[cmailOldMove] != NULL &&
13919             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13920                    "Black offers a draw" : "White offers a draw")) {
13921             TruncateGame();
13922             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13923             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13924         } else {
13925             DisplayError(_("There is no pending offer on this move"), 0);
13926             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13927         }
13928     } else {
13929         /* Not used for offers from chess program */
13930     }
13931 }
13932
13933 void
13934 DeclineEvent ()
13935 {
13936     /* Decline a pending offer of any kind from opponent */
13937
13938     if (appData.icsActive) {
13939         SendToICS(ics_prefix);
13940         SendToICS("decline\n");
13941     } else if (cmailMsgLoaded) {
13942         if (currentMove == cmailOldMove &&
13943             commentList[cmailOldMove] != NULL &&
13944             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13945                    "Black offers a draw" : "White offers a draw")) {
13946 #ifdef NOTDEF
13947             AppendComment(cmailOldMove, "Draw declined", TRUE);
13948             DisplayComment(cmailOldMove - 1, "Draw declined");
13949 #endif /*NOTDEF*/
13950         } else {
13951             DisplayError(_("There is no pending offer on this move"), 0);
13952         }
13953     } else {
13954         /* Not used for offers from chess program */
13955     }
13956 }
13957
13958 void
13959 RematchEvent ()
13960 {
13961     /* Issue ICS rematch command */
13962     if (appData.icsActive) {
13963         SendToICS(ics_prefix);
13964         SendToICS("rematch\n");
13965     }
13966 }
13967
13968 void
13969 CallFlagEvent ()
13970 {
13971     /* Call your opponent's flag (claim a win on time) */
13972     if (appData.icsActive) {
13973         SendToICS(ics_prefix);
13974         SendToICS("flag\n");
13975     } else {
13976         switch (gameMode) {
13977           default:
13978             return;
13979           case MachinePlaysWhite:
13980             if (whiteFlag) {
13981                 if (blackFlag)
13982                   GameEnds(GameIsDrawn, "Both players ran out of time",
13983                            GE_PLAYER);
13984                 else
13985                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13986             } else {
13987                 DisplayError(_("Your opponent is not out of time"), 0);
13988             }
13989             break;
13990           case MachinePlaysBlack:
13991             if (blackFlag) {
13992                 if (whiteFlag)
13993                   GameEnds(GameIsDrawn, "Both players ran out of time",
13994                            GE_PLAYER);
13995                 else
13996                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13997             } else {
13998                 DisplayError(_("Your opponent is not out of time"), 0);
13999             }
14000             break;
14001         }
14002     }
14003 }
14004
14005 void
14006 ClockClick (int which)
14007 {       // [HGM] code moved to back-end from winboard.c
14008         if(which) { // black clock
14009           if (gameMode == EditPosition || gameMode == IcsExamining) {
14010             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14011             SetBlackToPlayEvent();
14012           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14013           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14014           } else if (shiftKey) {
14015             AdjustClock(which, -1);
14016           } else if (gameMode == IcsPlayingWhite ||
14017                      gameMode == MachinePlaysBlack) {
14018             CallFlagEvent();
14019           }
14020         } else { // white clock
14021           if (gameMode == EditPosition || gameMode == IcsExamining) {
14022             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14023             SetWhiteToPlayEvent();
14024           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14025           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14026           } else if (shiftKey) {
14027             AdjustClock(which, -1);
14028           } else if (gameMode == IcsPlayingBlack ||
14029                    gameMode == MachinePlaysWhite) {
14030             CallFlagEvent();
14031           }
14032         }
14033 }
14034
14035 void
14036 DrawEvent ()
14037 {
14038     /* Offer draw or accept pending draw offer from opponent */
14039
14040     if (appData.icsActive) {
14041         /* Note: tournament rules require draw offers to be
14042            made after you make your move but before you punch
14043            your clock.  Currently ICS doesn't let you do that;
14044            instead, you immediately punch your clock after making
14045            a move, but you can offer a draw at any time. */
14046
14047         SendToICS(ics_prefix);
14048         SendToICS("draw\n");
14049         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14050     } else if (cmailMsgLoaded) {
14051         if (currentMove == cmailOldMove &&
14052             commentList[cmailOldMove] != NULL &&
14053             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14054                    "Black offers a draw" : "White offers a draw")) {
14055             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14056             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14057         } else if (currentMove == cmailOldMove + 1) {
14058             char *offer = WhiteOnMove(cmailOldMove) ?
14059               "White offers a draw" : "Black offers a draw";
14060             AppendComment(currentMove, offer, TRUE);
14061             DisplayComment(currentMove - 1, offer);
14062             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14063         } else {
14064             DisplayError(_("You must make your move before offering a draw"), 0);
14065             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14066         }
14067     } else if (first.offeredDraw) {
14068         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14069     } else {
14070         if (first.sendDrawOffers) {
14071             SendToProgram("draw\n", &first);
14072             userOfferedDraw = TRUE;
14073         }
14074     }
14075 }
14076
14077 void
14078 AdjournEvent ()
14079 {
14080     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14081
14082     if (appData.icsActive) {
14083         SendToICS(ics_prefix);
14084         SendToICS("adjourn\n");
14085     } else {
14086         /* Currently GNU Chess doesn't offer or accept Adjourns */
14087     }
14088 }
14089
14090
14091 void
14092 AbortEvent ()
14093 {
14094     /* Offer Abort or accept pending Abort offer from opponent */
14095
14096     if (appData.icsActive) {
14097         SendToICS(ics_prefix);
14098         SendToICS("abort\n");
14099     } else {
14100         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14101     }
14102 }
14103
14104 void
14105 ResignEvent ()
14106 {
14107     /* Resign.  You can do this even if it's not your turn. */
14108
14109     if (appData.icsActive) {
14110         SendToICS(ics_prefix);
14111         SendToICS("resign\n");
14112     } else {
14113         switch (gameMode) {
14114           case MachinePlaysWhite:
14115             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14116             break;
14117           case MachinePlaysBlack:
14118             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14119             break;
14120           case EditGame:
14121             if (cmailMsgLoaded) {
14122                 TruncateGame();
14123                 if (WhiteOnMove(cmailOldMove)) {
14124                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14125                 } else {
14126                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14127                 }
14128                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14129             }
14130             break;
14131           default:
14132             break;
14133         }
14134     }
14135 }
14136
14137
14138 void
14139 StopObservingEvent ()
14140 {
14141     /* Stop observing current games */
14142     SendToICS(ics_prefix);
14143     SendToICS("unobserve\n");
14144 }
14145
14146 void
14147 StopExaminingEvent ()
14148 {
14149     /* Stop observing current game */
14150     SendToICS(ics_prefix);
14151     SendToICS("unexamine\n");
14152 }
14153
14154 void
14155 ForwardInner (int target)
14156 {
14157     int limit;
14158
14159     if (appData.debugMode)
14160         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14161                 target, currentMove, forwardMostMove);
14162
14163     if (gameMode == EditPosition)
14164       return;
14165
14166     seekGraphUp = FALSE;
14167     MarkTargetSquares(1);
14168
14169     if (gameMode == PlayFromGameFile && !pausing)
14170       PauseEvent();
14171
14172     if (gameMode == IcsExamining && pausing)
14173       limit = pauseExamForwardMostMove;
14174     else
14175       limit = forwardMostMove;
14176
14177     if (target > limit) target = limit;
14178
14179     if (target > 0 && moveList[target - 1][0]) {
14180         int fromX, fromY, toX, toY;
14181         toX = moveList[target - 1][2] - AAA;
14182         toY = moveList[target - 1][3] - ONE;
14183         if (moveList[target - 1][1] == '@') {
14184             if (appData.highlightLastMove) {
14185                 SetHighlights(-1, -1, toX, toY);
14186             }
14187         } else {
14188             fromX = moveList[target - 1][0] - AAA;
14189             fromY = moveList[target - 1][1] - ONE;
14190             if (target == currentMove + 1) {
14191                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14192             }
14193             if (appData.highlightLastMove) {
14194                 SetHighlights(fromX, fromY, toX, toY);
14195             }
14196         }
14197     }
14198     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14199         gameMode == Training || gameMode == PlayFromGameFile ||
14200         gameMode == AnalyzeFile) {
14201         while (currentMove < target) {
14202             SendMoveToProgram(currentMove++, &first);
14203         }
14204     } else {
14205         currentMove = target;
14206     }
14207
14208     if (gameMode == EditGame || gameMode == EndOfGame) {
14209         whiteTimeRemaining = timeRemaining[0][currentMove];
14210         blackTimeRemaining = timeRemaining[1][currentMove];
14211     }
14212     DisplayBothClocks();
14213     DisplayMove(currentMove - 1);
14214     DrawPosition(FALSE, boards[currentMove]);
14215     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14216     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14217         DisplayComment(currentMove - 1, commentList[currentMove]);
14218     }
14219 }
14220
14221
14222 void
14223 ForwardEvent ()
14224 {
14225     if (gameMode == IcsExamining && !pausing) {
14226         SendToICS(ics_prefix);
14227         SendToICS("forward\n");
14228     } else {
14229         ForwardInner(currentMove + 1);
14230     }
14231 }
14232
14233 void
14234 ToEndEvent ()
14235 {
14236     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14237         /* to optimze, we temporarily turn off analysis mode while we feed
14238          * the remaining moves to the engine. Otherwise we get analysis output
14239          * after each move.
14240          */
14241         if (first.analysisSupport) {
14242           SendToProgram("exit\nforce\n", &first);
14243           first.analyzing = FALSE;
14244         }
14245     }
14246
14247     if (gameMode == IcsExamining && !pausing) {
14248         SendToICS(ics_prefix);
14249         SendToICS("forward 999999\n");
14250     } else {
14251         ForwardInner(forwardMostMove);
14252     }
14253
14254     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14255         /* we have fed all the moves, so reactivate analysis mode */
14256         SendToProgram("analyze\n", &first);
14257         first.analyzing = TRUE;
14258         /*first.maybeThinking = TRUE;*/
14259         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14260     }
14261 }
14262
14263 void
14264 BackwardInner (int target)
14265 {
14266     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14267
14268     if (appData.debugMode)
14269         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14270                 target, currentMove, forwardMostMove);
14271
14272     if (gameMode == EditPosition) return;
14273     seekGraphUp = FALSE;
14274     MarkTargetSquares(1);
14275     if (currentMove <= backwardMostMove) {
14276         ClearHighlights();
14277         DrawPosition(full_redraw, boards[currentMove]);
14278         return;
14279     }
14280     if (gameMode == PlayFromGameFile && !pausing)
14281       PauseEvent();
14282
14283     if (moveList[target][0]) {
14284         int fromX, fromY, toX, toY;
14285         toX = moveList[target][2] - AAA;
14286         toY = moveList[target][3] - ONE;
14287         if (moveList[target][1] == '@') {
14288             if (appData.highlightLastMove) {
14289                 SetHighlights(-1, -1, toX, toY);
14290             }
14291         } else {
14292             fromX = moveList[target][0] - AAA;
14293             fromY = moveList[target][1] - ONE;
14294             if (target == currentMove - 1) {
14295                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14296             }
14297             if (appData.highlightLastMove) {
14298                 SetHighlights(fromX, fromY, toX, toY);
14299             }
14300         }
14301     }
14302     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14303         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14304         while (currentMove > target) {
14305             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14306                 // null move cannot be undone. Reload program with move history before it.
14307                 int i;
14308                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14309                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14310                 }
14311                 SendBoard(&first, i); 
14312                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14313                 break;
14314             }
14315             SendToProgram("undo\n", &first);
14316             currentMove--;
14317         }
14318     } else {
14319         currentMove = target;
14320     }
14321
14322     if (gameMode == EditGame || gameMode == EndOfGame) {
14323         whiteTimeRemaining = timeRemaining[0][currentMove];
14324         blackTimeRemaining = timeRemaining[1][currentMove];
14325     }
14326     DisplayBothClocks();
14327     DisplayMove(currentMove - 1);
14328     DrawPosition(full_redraw, boards[currentMove]);
14329     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14330     // [HGM] PV info: routine tests if comment empty
14331     DisplayComment(currentMove - 1, commentList[currentMove]);
14332 }
14333
14334 void
14335 BackwardEvent ()
14336 {
14337     if (gameMode == IcsExamining && !pausing) {
14338         SendToICS(ics_prefix);
14339         SendToICS("backward\n");
14340     } else {
14341         BackwardInner(currentMove - 1);
14342     }
14343 }
14344
14345 void
14346 ToStartEvent ()
14347 {
14348     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14349         /* to optimize, we temporarily turn off analysis mode while we undo
14350          * all the moves. Otherwise we get analysis output after each undo.
14351          */
14352         if (first.analysisSupport) {
14353           SendToProgram("exit\nforce\n", &first);
14354           first.analyzing = FALSE;
14355         }
14356     }
14357
14358     if (gameMode == IcsExamining && !pausing) {
14359         SendToICS(ics_prefix);
14360         SendToICS("backward 999999\n");
14361     } else {
14362         BackwardInner(backwardMostMove);
14363     }
14364
14365     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14366         /* we have fed all the moves, so reactivate analysis mode */
14367         SendToProgram("analyze\n", &first);
14368         first.analyzing = TRUE;
14369         /*first.maybeThinking = TRUE;*/
14370         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14371     }
14372 }
14373
14374 void
14375 ToNrEvent (int to)
14376 {
14377   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14378   if (to >= forwardMostMove) to = forwardMostMove;
14379   if (to <= backwardMostMove) to = backwardMostMove;
14380   if (to < currentMove) {
14381     BackwardInner(to);
14382   } else {
14383     ForwardInner(to);
14384   }
14385 }
14386
14387 void
14388 RevertEvent (Boolean annotate)
14389 {
14390     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14391         return;
14392     }
14393     if (gameMode != IcsExamining) {
14394         DisplayError(_("You are not examining a game"), 0);
14395         return;
14396     }
14397     if (pausing) {
14398         DisplayError(_("You can't revert while pausing"), 0);
14399         return;
14400     }
14401     SendToICS(ics_prefix);
14402     SendToICS("revert\n");
14403 }
14404
14405 void
14406 RetractMoveEvent ()
14407 {
14408     switch (gameMode) {
14409       case MachinePlaysWhite:
14410       case MachinePlaysBlack:
14411         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14412             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14413             return;
14414         }
14415         if (forwardMostMove < 2) return;
14416         currentMove = forwardMostMove = forwardMostMove - 2;
14417         whiteTimeRemaining = timeRemaining[0][currentMove];
14418         blackTimeRemaining = timeRemaining[1][currentMove];
14419         DisplayBothClocks();
14420         DisplayMove(currentMove - 1);
14421         ClearHighlights();/*!! could figure this out*/
14422         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14423         SendToProgram("remove\n", &first);
14424         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14425         break;
14426
14427       case BeginningOfGame:
14428       default:
14429         break;
14430
14431       case IcsPlayingWhite:
14432       case IcsPlayingBlack:
14433         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14434             SendToICS(ics_prefix);
14435             SendToICS("takeback 2\n");
14436         } else {
14437             SendToICS(ics_prefix);
14438             SendToICS("takeback 1\n");
14439         }
14440         break;
14441     }
14442 }
14443
14444 void
14445 MoveNowEvent ()
14446 {
14447     ChessProgramState *cps;
14448
14449     switch (gameMode) {
14450       case MachinePlaysWhite:
14451         if (!WhiteOnMove(forwardMostMove)) {
14452             DisplayError(_("It is your turn"), 0);
14453             return;
14454         }
14455         cps = &first;
14456         break;
14457       case MachinePlaysBlack:
14458         if (WhiteOnMove(forwardMostMove)) {
14459             DisplayError(_("It is your turn"), 0);
14460             return;
14461         }
14462         cps = &first;
14463         break;
14464       case TwoMachinesPlay:
14465         if (WhiteOnMove(forwardMostMove) ==
14466             (first.twoMachinesColor[0] == 'w')) {
14467             cps = &first;
14468         } else {
14469             cps = &second;
14470         }
14471         break;
14472       case BeginningOfGame:
14473       default:
14474         return;
14475     }
14476     SendToProgram("?\n", cps);
14477 }
14478
14479 void
14480 TruncateGameEvent ()
14481 {
14482     EditGameEvent();
14483     if (gameMode != EditGame) return;
14484     TruncateGame();
14485 }
14486
14487 void
14488 TruncateGame ()
14489 {
14490     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14491     if (forwardMostMove > currentMove) {
14492         if (gameInfo.resultDetails != NULL) {
14493             free(gameInfo.resultDetails);
14494             gameInfo.resultDetails = NULL;
14495             gameInfo.result = GameUnfinished;
14496         }
14497         forwardMostMove = currentMove;
14498         HistorySet(parseList, backwardMostMove, forwardMostMove,
14499                    currentMove-1);
14500     }
14501 }
14502
14503 void
14504 HintEvent ()
14505 {
14506     if (appData.noChessProgram) return;
14507     switch (gameMode) {
14508       case MachinePlaysWhite:
14509         if (WhiteOnMove(forwardMostMove)) {
14510             DisplayError(_("Wait until your turn"), 0);
14511             return;
14512         }
14513         break;
14514       case BeginningOfGame:
14515       case MachinePlaysBlack:
14516         if (!WhiteOnMove(forwardMostMove)) {
14517             DisplayError(_("Wait until your turn"), 0);
14518             return;
14519         }
14520         break;
14521       default:
14522         DisplayError(_("No hint available"), 0);
14523         return;
14524     }
14525     SendToProgram("hint\n", &first);
14526     hintRequested = TRUE;
14527 }
14528
14529 void
14530 BookEvent ()
14531 {
14532     if (appData.noChessProgram) return;
14533     switch (gameMode) {
14534       case MachinePlaysWhite:
14535         if (WhiteOnMove(forwardMostMove)) {
14536             DisplayError(_("Wait until your turn"), 0);
14537             return;
14538         }
14539         break;
14540       case BeginningOfGame:
14541       case MachinePlaysBlack:
14542         if (!WhiteOnMove(forwardMostMove)) {
14543             DisplayError(_("Wait until your turn"), 0);
14544             return;
14545         }
14546         break;
14547       case EditPosition:
14548         EditPositionDone(TRUE);
14549         break;
14550       case TwoMachinesPlay:
14551         return;
14552       default:
14553         break;
14554     }
14555     SendToProgram("bk\n", &first);
14556     bookOutput[0] = NULLCHAR;
14557     bookRequested = TRUE;
14558 }
14559
14560 void
14561 AboutGameEvent ()
14562 {
14563     char *tags = PGNTags(&gameInfo);
14564     TagsPopUp(tags, CmailMsg());
14565     free(tags);
14566 }
14567
14568 /* end button procedures */
14569
14570 void
14571 PrintPosition (FILE *fp, int move)
14572 {
14573     int i, j;
14574
14575     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14576         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14577             char c = PieceToChar(boards[move][i][j]);
14578             fputc(c == 'x' ? '.' : c, fp);
14579             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14580         }
14581     }
14582     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14583       fprintf(fp, "white to play\n");
14584     else
14585       fprintf(fp, "black to play\n");
14586 }
14587
14588 void
14589 PrintOpponents (FILE *fp)
14590 {
14591     if (gameInfo.white != NULL) {
14592         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14593     } else {
14594         fprintf(fp, "\n");
14595     }
14596 }
14597
14598 /* Find last component of program's own name, using some heuristics */
14599 void
14600 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14601 {
14602     char *p, *q;
14603     int local = (strcmp(host, "localhost") == 0);
14604     while (!local && (p = strchr(prog, ';')) != NULL) {
14605         p++;
14606         while (*p == ' ') p++;
14607         prog = p;
14608     }
14609     if (*prog == '"' || *prog == '\'') {
14610         q = strchr(prog + 1, *prog);
14611     } else {
14612         q = strchr(prog, ' ');
14613     }
14614     if (q == NULL) q = prog + strlen(prog);
14615     p = q;
14616     while (p >= prog && *p != '/' && *p != '\\') p--;
14617     p++;
14618     if(p == prog && *p == '"') p++;
14619     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14620     memcpy(buf, p, q - p);
14621     buf[q - p] = NULLCHAR;
14622     if (!local) {
14623         strcat(buf, "@");
14624         strcat(buf, host);
14625     }
14626 }
14627
14628 char *
14629 TimeControlTagValue ()
14630 {
14631     char buf[MSG_SIZ];
14632     if (!appData.clockMode) {
14633       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14634     } else if (movesPerSession > 0) {
14635       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14636     } else if (timeIncrement == 0) {
14637       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14638     } else {
14639       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14640     }
14641     return StrSave(buf);
14642 }
14643
14644 void
14645 SetGameInfo ()
14646 {
14647     /* This routine is used only for certain modes */
14648     VariantClass v = gameInfo.variant;
14649     ChessMove r = GameUnfinished;
14650     char *p = NULL;
14651
14652     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14653         r = gameInfo.result;
14654         p = gameInfo.resultDetails;
14655         gameInfo.resultDetails = NULL;
14656     }
14657     ClearGameInfo(&gameInfo);
14658     gameInfo.variant = v;
14659
14660     switch (gameMode) {
14661       case MachinePlaysWhite:
14662         gameInfo.event = StrSave( appData.pgnEventHeader );
14663         gameInfo.site = StrSave(HostName());
14664         gameInfo.date = PGNDate();
14665         gameInfo.round = StrSave("-");
14666         gameInfo.white = StrSave(first.tidy);
14667         gameInfo.black = StrSave(UserName());
14668         gameInfo.timeControl = TimeControlTagValue();
14669         break;
14670
14671       case MachinePlaysBlack:
14672         gameInfo.event = StrSave( appData.pgnEventHeader );
14673         gameInfo.site = StrSave(HostName());
14674         gameInfo.date = PGNDate();
14675         gameInfo.round = StrSave("-");
14676         gameInfo.white = StrSave(UserName());
14677         gameInfo.black = StrSave(first.tidy);
14678         gameInfo.timeControl = TimeControlTagValue();
14679         break;
14680
14681       case TwoMachinesPlay:
14682         gameInfo.event = StrSave( appData.pgnEventHeader );
14683         gameInfo.site = StrSave(HostName());
14684         gameInfo.date = PGNDate();
14685         if (roundNr > 0) {
14686             char buf[MSG_SIZ];
14687             snprintf(buf, MSG_SIZ, "%d", roundNr);
14688             gameInfo.round = StrSave(buf);
14689         } else {
14690             gameInfo.round = StrSave("-");
14691         }
14692         if (first.twoMachinesColor[0] == 'w') {
14693             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14694             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14695         } else {
14696             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14697             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14698         }
14699         gameInfo.timeControl = TimeControlTagValue();
14700         break;
14701
14702       case EditGame:
14703         gameInfo.event = StrSave("Edited game");
14704         gameInfo.site = StrSave(HostName());
14705         gameInfo.date = PGNDate();
14706         gameInfo.round = StrSave("-");
14707         gameInfo.white = StrSave("-");
14708         gameInfo.black = StrSave("-");
14709         gameInfo.result = r;
14710         gameInfo.resultDetails = p;
14711         break;
14712
14713       case EditPosition:
14714         gameInfo.event = StrSave("Edited position");
14715         gameInfo.site = StrSave(HostName());
14716         gameInfo.date = PGNDate();
14717         gameInfo.round = StrSave("-");
14718         gameInfo.white = StrSave("-");
14719         gameInfo.black = StrSave("-");
14720         break;
14721
14722       case IcsPlayingWhite:
14723       case IcsPlayingBlack:
14724       case IcsObserving:
14725       case IcsExamining:
14726         break;
14727
14728       case PlayFromGameFile:
14729         gameInfo.event = StrSave("Game from non-PGN file");
14730         gameInfo.site = StrSave(HostName());
14731         gameInfo.date = PGNDate();
14732         gameInfo.round = StrSave("-");
14733         gameInfo.white = StrSave("?");
14734         gameInfo.black = StrSave("?");
14735         break;
14736
14737       default:
14738         break;
14739     }
14740 }
14741
14742 void
14743 ReplaceComment (int index, char *text)
14744 {
14745     int len;
14746     char *p;
14747     float score;
14748
14749     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14750        pvInfoList[index-1].depth == len &&
14751        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14752        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14753     while (*text == '\n') text++;
14754     len = strlen(text);
14755     while (len > 0 && text[len - 1] == '\n') len--;
14756
14757     if (commentList[index] != NULL)
14758       free(commentList[index]);
14759
14760     if (len == 0) {
14761         commentList[index] = NULL;
14762         return;
14763     }
14764   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14765       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14766       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14767     commentList[index] = (char *) malloc(len + 2);
14768     strncpy(commentList[index], text, len);
14769     commentList[index][len] = '\n';
14770     commentList[index][len + 1] = NULLCHAR;
14771   } else {
14772     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14773     char *p;
14774     commentList[index] = (char *) malloc(len + 7);
14775     safeStrCpy(commentList[index], "{\n", 3);
14776     safeStrCpy(commentList[index]+2, text, len+1);
14777     commentList[index][len+2] = NULLCHAR;
14778     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14779     strcat(commentList[index], "\n}\n");
14780   }
14781 }
14782
14783 void
14784 CrushCRs (char *text)
14785 {
14786   char *p = text;
14787   char *q = text;
14788   char ch;
14789
14790   do {
14791     ch = *p++;
14792     if (ch == '\r') continue;
14793     *q++ = ch;
14794   } while (ch != '\0');
14795 }
14796
14797 void
14798 AppendComment (int index, char *text, Boolean addBraces)
14799 /* addBraces  tells if we should add {} */
14800 {
14801     int oldlen, len;
14802     char *old;
14803
14804 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14805     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14806
14807     CrushCRs(text);
14808     while (*text == '\n') text++;
14809     len = strlen(text);
14810     while (len > 0 && text[len - 1] == '\n') len--;
14811     text[len] = NULLCHAR;
14812
14813     if (len == 0) return;
14814
14815     if (commentList[index] != NULL) {
14816       Boolean addClosingBrace = addBraces;
14817         old = commentList[index];
14818         oldlen = strlen(old);
14819         while(commentList[index][oldlen-1] ==  '\n')
14820           commentList[index][--oldlen] = NULLCHAR;
14821         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14822         safeStrCpy(commentList[index], old, oldlen + len + 6);
14823         free(old);
14824         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14825         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14826           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14827           while (*text == '\n') { text++; len--; }
14828           commentList[index][--oldlen] = NULLCHAR;
14829       }
14830         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14831         else          strcat(commentList[index], "\n");
14832         strcat(commentList[index], text);
14833         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14834         else          strcat(commentList[index], "\n");
14835     } else {
14836         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14837         if(addBraces)
14838           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14839         else commentList[index][0] = NULLCHAR;
14840         strcat(commentList[index], text);
14841         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14842         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14843     }
14844 }
14845
14846 static char *
14847 FindStr (char * text, char * sub_text)
14848 {
14849     char * result = strstr( text, sub_text );
14850
14851     if( result != NULL ) {
14852         result += strlen( sub_text );
14853     }
14854
14855     return result;
14856 }
14857
14858 /* [AS] Try to extract PV info from PGN comment */
14859 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14860 char *
14861 GetInfoFromComment (int index, char * text)
14862 {
14863     char * sep = text, *p;
14864
14865     if( text != NULL && index > 0 ) {
14866         int score = 0;
14867         int depth = 0;
14868         int time = -1, sec = 0, deci;
14869         char * s_eval = FindStr( text, "[%eval " );
14870         char * s_emt = FindStr( text, "[%emt " );
14871
14872         if( s_eval != NULL || s_emt != NULL ) {
14873             /* New style */
14874             char delim;
14875
14876             if( s_eval != NULL ) {
14877                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14878                     return text;
14879                 }
14880
14881                 if( delim != ']' ) {
14882                     return text;
14883                 }
14884             }
14885
14886             if( s_emt != NULL ) {
14887             }
14888                 return text;
14889         }
14890         else {
14891             /* We expect something like: [+|-]nnn.nn/dd */
14892             int score_lo = 0;
14893
14894             if(*text != '{') return text; // [HGM] braces: must be normal comment
14895
14896             sep = strchr( text, '/' );
14897             if( sep == NULL || sep < (text+4) ) {
14898                 return text;
14899             }
14900
14901             p = text;
14902             if(p[1] == '(') { // comment starts with PV
14903                p = strchr(p, ')'); // locate end of PV
14904                if(p == NULL || sep < p+5) return text;
14905                // at this point we have something like "{(.*) +0.23/6 ..."
14906                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14907                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14908                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14909             }
14910             time = -1; sec = -1; deci = -1;
14911             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14912                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14913                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14914                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14915                 return text;
14916             }
14917
14918             if( score_lo < 0 || score_lo >= 100 ) {
14919                 return text;
14920             }
14921
14922             if(sec >= 0) time = 600*time + 10*sec; else
14923             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14924
14925             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14926
14927             /* [HGM] PV time: now locate end of PV info */
14928             while( *++sep >= '0' && *sep <= '9'); // strip depth
14929             if(time >= 0)
14930             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14931             if(sec >= 0)
14932             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14933             if(deci >= 0)
14934             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14935             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
14936         }
14937
14938         if( depth <= 0 ) {
14939             return text;
14940         }
14941
14942         if( time < 0 ) {
14943             time = -1;
14944         }
14945
14946         pvInfoList[index-1].depth = depth;
14947         pvInfoList[index-1].score = score;
14948         pvInfoList[index-1].time  = 10*time; // centi-sec
14949         if(*sep == '}') *sep = 0; else *--sep = '{';
14950         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14951     }
14952     return sep;
14953 }
14954
14955 void
14956 SendToProgram (char *message, ChessProgramState *cps)
14957 {
14958     int count, outCount, error;
14959     char buf[MSG_SIZ];
14960
14961     if (cps->pr == NoProc) return;
14962     Attention(cps);
14963
14964     if (appData.debugMode) {
14965         TimeMark now;
14966         GetTimeMark(&now);
14967         fprintf(debugFP, "%ld >%-6s: %s",
14968                 SubtractTimeMarks(&now, &programStartTime),
14969                 cps->which, message);
14970     }
14971
14972     count = strlen(message);
14973     outCount = OutputToProcess(cps->pr, message, count, &error);
14974     if (outCount < count && !exiting
14975                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14976       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14977       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14978         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14979             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14980                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14981                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14982                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14983             } else {
14984                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14985                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14986                 gameInfo.result = res;
14987             }
14988             gameInfo.resultDetails = StrSave(buf);
14989         }
14990         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14991         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14992     }
14993 }
14994
14995 void
14996 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
14997 {
14998     char *end_str;
14999     char buf[MSG_SIZ];
15000     ChessProgramState *cps = (ChessProgramState *)closure;
15001
15002     if (isr != cps->isr) return; /* Killed intentionally */
15003     if (count <= 0) {
15004         if (count == 0) {
15005             RemoveInputSource(cps->isr);
15006             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15007             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15008                     _(cps->which), cps->program);
15009         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15010                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15011                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15012                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15013                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15014                 } else {
15015                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15016                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15017                     gameInfo.result = res;
15018                 }
15019                 gameInfo.resultDetails = StrSave(buf);
15020             }
15021             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15022             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15023         } else {
15024             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15025                     _(cps->which), cps->program);
15026             RemoveInputSource(cps->isr);
15027
15028             /* [AS] Program is misbehaving badly... kill it */
15029             if( count == -2 ) {
15030                 DestroyChildProcess( cps->pr, 9 );
15031                 cps->pr = NoProc;
15032             }
15033
15034             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15035         }
15036         return;
15037     }
15038
15039     if ((end_str = strchr(message, '\r')) != NULL)
15040       *end_str = NULLCHAR;
15041     if ((end_str = strchr(message, '\n')) != NULL)
15042       *end_str = NULLCHAR;
15043
15044     if (appData.debugMode) {
15045         TimeMark now; int print = 1;
15046         char *quote = ""; char c; int i;
15047
15048         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15049                 char start = message[0];
15050                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15051                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15052                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15053                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15054                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15055                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15056                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15057                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15058                    sscanf(message, "hint: %c", &c)!=1 && 
15059                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15060                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15061                     print = (appData.engineComments >= 2);
15062                 }
15063                 message[0] = start; // restore original message
15064         }
15065         if(print) {
15066                 GetTimeMark(&now);
15067                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15068                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15069                         quote,
15070                         message);
15071         }
15072     }
15073
15074     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15075     if (appData.icsEngineAnalyze) {
15076         if (strstr(message, "whisper") != NULL ||
15077              strstr(message, "kibitz") != NULL ||
15078             strstr(message, "tellics") != NULL) return;
15079     }
15080
15081     HandleMachineMove(message, cps);
15082 }
15083
15084
15085 void
15086 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15087 {
15088     char buf[MSG_SIZ];
15089     int seconds;
15090
15091     if( timeControl_2 > 0 ) {
15092         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15093             tc = timeControl_2;
15094         }
15095     }
15096     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15097     inc /= cps->timeOdds;
15098     st  /= cps->timeOdds;
15099
15100     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15101
15102     if (st > 0) {
15103       /* Set exact time per move, normally using st command */
15104       if (cps->stKludge) {
15105         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15106         seconds = st % 60;
15107         if (seconds == 0) {
15108           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15109         } else {
15110           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15111         }
15112       } else {
15113         snprintf(buf, MSG_SIZ, "st %d\n", st);
15114       }
15115     } else {
15116       /* Set conventional or incremental time control, using level command */
15117       if (seconds == 0) {
15118         /* Note old gnuchess bug -- minutes:seconds used to not work.
15119            Fixed in later versions, but still avoid :seconds
15120            when seconds is 0. */
15121         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15122       } else {
15123         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15124                  seconds, inc/1000.);
15125       }
15126     }
15127     SendToProgram(buf, cps);
15128
15129     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15130     /* Orthogonally, limit search to given depth */
15131     if (sd > 0) {
15132       if (cps->sdKludge) {
15133         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15134       } else {
15135         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15136       }
15137       SendToProgram(buf, cps);
15138     }
15139
15140     if(cps->nps >= 0) { /* [HGM] nps */
15141         if(cps->supportsNPS == FALSE)
15142           cps->nps = -1; // don't use if engine explicitly says not supported!
15143         else {
15144           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15145           SendToProgram(buf, cps);
15146         }
15147     }
15148 }
15149
15150 ChessProgramState *
15151 WhitePlayer ()
15152 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15153 {
15154     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15155        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15156         return &second;
15157     return &first;
15158 }
15159
15160 void
15161 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15162 {
15163     char message[MSG_SIZ];
15164     long time, otime;
15165
15166     /* Note: this routine must be called when the clocks are stopped
15167        or when they have *just* been set or switched; otherwise
15168        it will be off by the time since the current tick started.
15169     */
15170     if (machineWhite) {
15171         time = whiteTimeRemaining / 10;
15172         otime = blackTimeRemaining / 10;
15173     } else {
15174         time = blackTimeRemaining / 10;
15175         otime = whiteTimeRemaining / 10;
15176     }
15177     /* [HGM] translate opponent's time by time-odds factor */
15178     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15179     if (appData.debugMode) {
15180         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15181     }
15182
15183     if (time <= 0) time = 1;
15184     if (otime <= 0) otime = 1;
15185
15186     snprintf(message, MSG_SIZ, "time %ld\n", time);
15187     SendToProgram(message, cps);
15188
15189     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15190     SendToProgram(message, cps);
15191 }
15192
15193 int
15194 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15195 {
15196   char buf[MSG_SIZ];
15197   int len = strlen(name);
15198   int val;
15199
15200   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15201     (*p) += len + 1;
15202     sscanf(*p, "%d", &val);
15203     *loc = (val != 0);
15204     while (**p && **p != ' ')
15205       (*p)++;
15206     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15207     SendToProgram(buf, cps);
15208     return TRUE;
15209   }
15210   return FALSE;
15211 }
15212
15213 int
15214 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15215 {
15216   char buf[MSG_SIZ];
15217   int len = strlen(name);
15218   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15219     (*p) += len + 1;
15220     sscanf(*p, "%d", loc);
15221     while (**p && **p != ' ') (*p)++;
15222     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15223     SendToProgram(buf, cps);
15224     return TRUE;
15225   }
15226   return FALSE;
15227 }
15228
15229 int
15230 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15231 {
15232   char buf[MSG_SIZ];
15233   int len = strlen(name);
15234   if (strncmp((*p), name, len) == 0
15235       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15236     (*p) += len + 2;
15237     sscanf(*p, "%[^\"]", loc);
15238     while (**p && **p != '\"') (*p)++;
15239     if (**p == '\"') (*p)++;
15240     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15241     SendToProgram(buf, cps);
15242     return TRUE;
15243   }
15244   return FALSE;
15245 }
15246
15247 int
15248 ParseOption (Option *opt, ChessProgramState *cps)
15249 // [HGM] options: process the string that defines an engine option, and determine
15250 // name, type, default value, and allowed value range
15251 {
15252         char *p, *q, buf[MSG_SIZ];
15253         int n, min = (-1)<<31, max = 1<<31, def;
15254
15255         if(p = strstr(opt->name, " -spin ")) {
15256             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15257             if(max < min) max = min; // enforce consistency
15258             if(def < min) def = min;
15259             if(def > max) def = max;
15260             opt->value = def;
15261             opt->min = min;
15262             opt->max = max;
15263             opt->type = Spin;
15264         } else if((p = strstr(opt->name, " -slider "))) {
15265             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15266             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15267             if(max < min) max = min; // enforce consistency
15268             if(def < min) def = min;
15269             if(def > max) def = max;
15270             opt->value = def;
15271             opt->min = min;
15272             opt->max = max;
15273             opt->type = Spin; // Slider;
15274         } else if((p = strstr(opt->name, " -string "))) {
15275             opt->textValue = p+9;
15276             opt->type = TextBox;
15277         } else if((p = strstr(opt->name, " -file "))) {
15278             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15279             opt->textValue = p+7;
15280             opt->type = FileName; // FileName;
15281         } else if((p = strstr(opt->name, " -path "))) {
15282             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15283             opt->textValue = p+7;
15284             opt->type = PathName; // PathName;
15285         } else if(p = strstr(opt->name, " -check ")) {
15286             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15287             opt->value = (def != 0);
15288             opt->type = CheckBox;
15289         } else if(p = strstr(opt->name, " -combo ")) {
15290             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15291             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15292             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15293             opt->value = n = 0;
15294             while(q = StrStr(q, " /// ")) {
15295                 n++; *q = 0;    // count choices, and null-terminate each of them
15296                 q += 5;
15297                 if(*q == '*') { // remember default, which is marked with * prefix
15298                     q++;
15299                     opt->value = n;
15300                 }
15301                 cps->comboList[cps->comboCnt++] = q;
15302             }
15303             cps->comboList[cps->comboCnt++] = NULL;
15304             opt->max = n + 1;
15305             opt->type = ComboBox;
15306         } else if(p = strstr(opt->name, " -button")) {
15307             opt->type = Button;
15308         } else if(p = strstr(opt->name, " -save")) {
15309             opt->type = SaveButton;
15310         } else return FALSE;
15311         *p = 0; // terminate option name
15312         // now look if the command-line options define a setting for this engine option.
15313         if(cps->optionSettings && cps->optionSettings[0])
15314             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15315         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15316           snprintf(buf, MSG_SIZ, "option %s", p);
15317                 if(p = strstr(buf, ",")) *p = 0;
15318                 if(q = strchr(buf, '=')) switch(opt->type) {
15319                     case ComboBox:
15320                         for(n=0; n<opt->max; n++)
15321                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15322                         break;
15323                     case TextBox:
15324                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15325                         break;
15326                     case Spin:
15327                     case CheckBox:
15328                         opt->value = atoi(q+1);
15329                     default:
15330                         break;
15331                 }
15332                 strcat(buf, "\n");
15333                 SendToProgram(buf, cps);
15334         }
15335         return TRUE;
15336 }
15337
15338 void
15339 FeatureDone (ChessProgramState *cps, int val)
15340 {
15341   DelayedEventCallback cb = GetDelayedEvent();
15342   if ((cb == InitBackEnd3 && cps == &first) ||
15343       (cb == SettingsMenuIfReady && cps == &second) ||
15344       (cb == LoadEngine) ||
15345       (cb == TwoMachinesEventIfReady)) {
15346     CancelDelayedEvent();
15347     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15348   }
15349   cps->initDone = val;
15350 }
15351
15352 /* Parse feature command from engine */
15353 void
15354 ParseFeatures (char *args, ChessProgramState *cps)
15355 {
15356   char *p = args;
15357   char *q;
15358   int val;
15359   char buf[MSG_SIZ];
15360
15361   for (;;) {
15362     while (*p == ' ') p++;
15363     if (*p == NULLCHAR) return;
15364
15365     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15366     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15367     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15368     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15369     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15370     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15371     if (BoolFeature(&p, "reuse", &val, cps)) {
15372       /* Engine can disable reuse, but can't enable it if user said no */
15373       if (!val) cps->reuse = FALSE;
15374       continue;
15375     }
15376     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15377     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15378       if (gameMode == TwoMachinesPlay) {
15379         DisplayTwoMachinesTitle();
15380       } else {
15381         DisplayTitle("");
15382       }
15383       continue;
15384     }
15385     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15386     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15387     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15388     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15389     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15390     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15391     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15392     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15393     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15394     if (IntFeature(&p, "done", &val, cps)) {
15395       FeatureDone(cps, val);
15396       continue;
15397     }
15398     /* Added by Tord: */
15399     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15400     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15401     /* End of additions by Tord */
15402
15403     /* [HGM] added features: */
15404     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15405     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15406     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15407     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15408     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15409     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15410     if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15411         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15412           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15413             SendToProgram(buf, cps);
15414             continue;
15415         }
15416         if(cps->nrOptions >= MAX_OPTIONS) {
15417             cps->nrOptions--;
15418             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15419             DisplayError(buf, 0);
15420         }
15421         continue;
15422     }
15423     /* End of additions by HGM */
15424
15425     /* unknown feature: complain and skip */
15426     q = p;
15427     while (*q && *q != '=') q++;
15428     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15429     SendToProgram(buf, cps);
15430     p = q;
15431     if (*p == '=') {
15432       p++;
15433       if (*p == '\"') {
15434         p++;
15435         while (*p && *p != '\"') p++;
15436         if (*p == '\"') p++;
15437       } else {
15438         while (*p && *p != ' ') p++;
15439       }
15440     }
15441   }
15442
15443 }
15444
15445 void
15446 PeriodicUpdatesEvent (int newState)
15447 {
15448     if (newState == appData.periodicUpdates)
15449       return;
15450
15451     appData.periodicUpdates=newState;
15452
15453     /* Display type changes, so update it now */
15454 //    DisplayAnalysis();
15455
15456     /* Get the ball rolling again... */
15457     if (newState) {
15458         AnalysisPeriodicEvent(1);
15459         StartAnalysisClock();
15460     }
15461 }
15462
15463 void
15464 PonderNextMoveEvent (int newState)
15465 {
15466     if (newState == appData.ponderNextMove) return;
15467     if (gameMode == EditPosition) EditPositionDone(TRUE);
15468     if (newState) {
15469         SendToProgram("hard\n", &first);
15470         if (gameMode == TwoMachinesPlay) {
15471             SendToProgram("hard\n", &second);
15472         }
15473     } else {
15474         SendToProgram("easy\n", &first);
15475         thinkOutput[0] = NULLCHAR;
15476         if (gameMode == TwoMachinesPlay) {
15477             SendToProgram("easy\n", &second);
15478         }
15479     }
15480     appData.ponderNextMove = newState;
15481 }
15482
15483 void
15484 NewSettingEvent (int option, int *feature, char *command, int value)
15485 {
15486     char buf[MSG_SIZ];
15487
15488     if (gameMode == EditPosition) EditPositionDone(TRUE);
15489     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15490     if(feature == NULL || *feature) SendToProgram(buf, &first);
15491     if (gameMode == TwoMachinesPlay) {
15492         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15493     }
15494 }
15495
15496 void
15497 ShowThinkingEvent ()
15498 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15499 {
15500     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15501     int newState = appData.showThinking
15502         // [HGM] thinking: other features now need thinking output as well
15503         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15504
15505     if (oldState == newState) return;
15506     oldState = newState;
15507     if (gameMode == EditPosition) EditPositionDone(TRUE);
15508     if (oldState) {
15509         SendToProgram("post\n", &first);
15510         if (gameMode == TwoMachinesPlay) {
15511             SendToProgram("post\n", &second);
15512         }
15513     } else {
15514         SendToProgram("nopost\n", &first);
15515         thinkOutput[0] = NULLCHAR;
15516         if (gameMode == TwoMachinesPlay) {
15517             SendToProgram("nopost\n", &second);
15518         }
15519     }
15520 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15521 }
15522
15523 void
15524 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15525 {
15526   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15527   if (pr == NoProc) return;
15528   AskQuestion(title, question, replyPrefix, pr);
15529 }
15530
15531 void
15532 TypeInEvent (char firstChar)
15533 {
15534     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15535         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15536         gameMode == AnalyzeMode || gameMode == EditGame || 
15537         gameMode == EditPosition || gameMode == IcsExamining ||
15538         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15539         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15540                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15541                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15542         gameMode == Training) PopUpMoveDialog(firstChar);
15543 }
15544
15545 void
15546 TypeInDoneEvent (char *move)
15547 {
15548         Board board;
15549         int n, fromX, fromY, toX, toY;
15550         char promoChar;
15551         ChessMove moveType;
15552
15553         // [HGM] FENedit
15554         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15555                 EditPositionPasteFEN(move);
15556                 return;
15557         }
15558         // [HGM] movenum: allow move number to be typed in any mode
15559         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15560           ToNrEvent(2*n-1);
15561           return;
15562         }
15563
15564       if (gameMode != EditGame && currentMove != forwardMostMove && 
15565         gameMode != Training) {
15566         DisplayMoveError(_("Displayed move is not current"));
15567       } else {
15568         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15569           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15570         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15571         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15572           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15573           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15574         } else {
15575           DisplayMoveError(_("Could not parse move"));
15576         }
15577       }
15578 }
15579
15580 void
15581 DisplayMove (int moveNumber)
15582 {
15583     char message[MSG_SIZ];
15584     char res[MSG_SIZ];
15585     char cpThinkOutput[MSG_SIZ];
15586
15587     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15588
15589     if (moveNumber == forwardMostMove - 1 ||
15590         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15591
15592         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15593
15594         if (strchr(cpThinkOutput, '\n')) {
15595             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15596         }
15597     } else {
15598         *cpThinkOutput = NULLCHAR;
15599     }
15600
15601     /* [AS] Hide thinking from human user */
15602     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15603         *cpThinkOutput = NULLCHAR;
15604         if( thinkOutput[0] != NULLCHAR ) {
15605             int i;
15606
15607             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15608                 cpThinkOutput[i] = '.';
15609             }
15610             cpThinkOutput[i] = NULLCHAR;
15611             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15612         }
15613     }
15614
15615     if (moveNumber == forwardMostMove - 1 &&
15616         gameInfo.resultDetails != NULL) {
15617         if (gameInfo.resultDetails[0] == NULLCHAR) {
15618           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15619         } else {
15620           snprintf(res, MSG_SIZ, " {%s} %s",
15621                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15622         }
15623     } else {
15624         res[0] = NULLCHAR;
15625     }
15626
15627     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15628         DisplayMessage(res, cpThinkOutput);
15629     } else {
15630       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15631                 WhiteOnMove(moveNumber) ? " " : ".. ",
15632                 parseList[moveNumber], res);
15633         DisplayMessage(message, cpThinkOutput);
15634     }
15635 }
15636
15637 void
15638 DisplayComment (int moveNumber, char *text)
15639 {
15640     char title[MSG_SIZ];
15641
15642     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15643       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15644     } else {
15645       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15646               WhiteOnMove(moveNumber) ? " " : ".. ",
15647               parseList[moveNumber]);
15648     }
15649     if (text != NULL && (appData.autoDisplayComment || commentUp))
15650         CommentPopUp(title, text);
15651 }
15652
15653 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15654  * might be busy thinking or pondering.  It can be omitted if your
15655  * gnuchess is configured to stop thinking immediately on any user
15656  * input.  However, that gnuchess feature depends on the FIONREAD
15657  * ioctl, which does not work properly on some flavors of Unix.
15658  */
15659 void
15660 Attention (ChessProgramState *cps)
15661 {
15662 #if ATTENTION
15663     if (!cps->useSigint) return;
15664     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15665     switch (gameMode) {
15666       case MachinePlaysWhite:
15667       case MachinePlaysBlack:
15668       case TwoMachinesPlay:
15669       case IcsPlayingWhite:
15670       case IcsPlayingBlack:
15671       case AnalyzeMode:
15672       case AnalyzeFile:
15673         /* Skip if we know it isn't thinking */
15674         if (!cps->maybeThinking) return;
15675         if (appData.debugMode)
15676           fprintf(debugFP, "Interrupting %s\n", cps->which);
15677         InterruptChildProcess(cps->pr);
15678         cps->maybeThinking = FALSE;
15679         break;
15680       default:
15681         break;
15682     }
15683 #endif /*ATTENTION*/
15684 }
15685
15686 int
15687 CheckFlags ()
15688 {
15689     if (whiteTimeRemaining <= 0) {
15690         if (!whiteFlag) {
15691             whiteFlag = TRUE;
15692             if (appData.icsActive) {
15693                 if (appData.autoCallFlag &&
15694                     gameMode == IcsPlayingBlack && !blackFlag) {
15695                   SendToICS(ics_prefix);
15696                   SendToICS("flag\n");
15697                 }
15698             } else {
15699                 if (blackFlag) {
15700                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15701                 } else {
15702                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15703                     if (appData.autoCallFlag) {
15704                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15705                         return TRUE;
15706                     }
15707                 }
15708             }
15709         }
15710     }
15711     if (blackTimeRemaining <= 0) {
15712         if (!blackFlag) {
15713             blackFlag = TRUE;
15714             if (appData.icsActive) {
15715                 if (appData.autoCallFlag &&
15716                     gameMode == IcsPlayingWhite && !whiteFlag) {
15717                   SendToICS(ics_prefix);
15718                   SendToICS("flag\n");
15719                 }
15720             } else {
15721                 if (whiteFlag) {
15722                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15723                 } else {
15724                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15725                     if (appData.autoCallFlag) {
15726                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15727                         return TRUE;
15728                     }
15729                 }
15730             }
15731         }
15732     }
15733     return FALSE;
15734 }
15735
15736 void
15737 CheckTimeControl ()
15738 {
15739     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15740         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15741
15742     /*
15743      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15744      */
15745     if ( !WhiteOnMove(forwardMostMove) ) {
15746         /* White made time control */
15747         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15748         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15749         /* [HGM] time odds: correct new time quota for time odds! */
15750                                             / WhitePlayer()->timeOdds;
15751         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15752     } else {
15753         lastBlack -= blackTimeRemaining;
15754         /* Black made time control */
15755         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15756                                             / WhitePlayer()->other->timeOdds;
15757         lastWhite = whiteTimeRemaining;
15758     }
15759 }
15760
15761 void
15762 DisplayBothClocks ()
15763 {
15764     int wom = gameMode == EditPosition ?
15765       !blackPlaysFirst : WhiteOnMove(currentMove);
15766     DisplayWhiteClock(whiteTimeRemaining, wom);
15767     DisplayBlackClock(blackTimeRemaining, !wom);
15768 }
15769
15770
15771 /* Timekeeping seems to be a portability nightmare.  I think everyone
15772    has ftime(), but I'm really not sure, so I'm including some ifdefs
15773    to use other calls if you don't.  Clocks will be less accurate if
15774    you have neither ftime nor gettimeofday.
15775 */
15776
15777 /* VS 2008 requires the #include outside of the function */
15778 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15779 #include <sys/timeb.h>
15780 #endif
15781
15782 /* Get the current time as a TimeMark */
15783 void
15784 GetTimeMark (TimeMark *tm)
15785 {
15786 #if HAVE_GETTIMEOFDAY
15787
15788     struct timeval timeVal;
15789     struct timezone timeZone;
15790
15791     gettimeofday(&timeVal, &timeZone);
15792     tm->sec = (long) timeVal.tv_sec;
15793     tm->ms = (int) (timeVal.tv_usec / 1000L);
15794
15795 #else /*!HAVE_GETTIMEOFDAY*/
15796 #if HAVE_FTIME
15797
15798 // include <sys/timeb.h> / moved to just above start of function
15799     struct timeb timeB;
15800
15801     ftime(&timeB);
15802     tm->sec = (long) timeB.time;
15803     tm->ms = (int) timeB.millitm;
15804
15805 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15806     tm->sec = (long) time(NULL);
15807     tm->ms = 0;
15808 #endif
15809 #endif
15810 }
15811
15812 /* Return the difference in milliseconds between two
15813    time marks.  We assume the difference will fit in a long!
15814 */
15815 long
15816 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
15817 {
15818     return 1000L*(tm2->sec - tm1->sec) +
15819            (long) (tm2->ms - tm1->ms);
15820 }
15821
15822
15823 /*
15824  * Code to manage the game clocks.
15825  *
15826  * In tournament play, black starts the clock and then white makes a move.
15827  * We give the human user a slight advantage if he is playing white---the
15828  * clocks don't run until he makes his first move, so it takes zero time.
15829  * Also, we don't account for network lag, so we could get out of sync
15830  * with GNU Chess's clock -- but then, referees are always right.
15831  */
15832
15833 static TimeMark tickStartTM;
15834 static long intendedTickLength;
15835
15836 long
15837 NextTickLength (long timeRemaining)
15838 {
15839     long nominalTickLength, nextTickLength;
15840
15841     if (timeRemaining > 0L && timeRemaining <= 10000L)
15842       nominalTickLength = 100L;
15843     else
15844       nominalTickLength = 1000L;
15845     nextTickLength = timeRemaining % nominalTickLength;
15846     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15847
15848     return nextTickLength;
15849 }
15850
15851 /* Adjust clock one minute up or down */
15852 void
15853 AdjustClock (Boolean which, int dir)
15854 {
15855     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15856     if(which) blackTimeRemaining += 60000*dir;
15857     else      whiteTimeRemaining += 60000*dir;
15858     DisplayBothClocks();
15859     adjustedClock = TRUE;
15860 }
15861
15862 /* Stop clocks and reset to a fresh time control */
15863 void
15864 ResetClocks ()
15865 {
15866     (void) StopClockTimer();
15867     if (appData.icsActive) {
15868         whiteTimeRemaining = blackTimeRemaining = 0;
15869     } else if (searchTime) {
15870         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15871         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15872     } else { /* [HGM] correct new time quote for time odds */
15873         whiteTC = blackTC = fullTimeControlString;
15874         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15875         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15876     }
15877     if (whiteFlag || blackFlag) {
15878         DisplayTitle("");
15879         whiteFlag = blackFlag = FALSE;
15880     }
15881     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15882     DisplayBothClocks();
15883     adjustedClock = FALSE;
15884 }
15885
15886 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15887
15888 /* Decrement running clock by amount of time that has passed */
15889 void
15890 DecrementClocks ()
15891 {
15892     long timeRemaining;
15893     long lastTickLength, fudge;
15894     TimeMark now;
15895
15896     if (!appData.clockMode) return;
15897     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15898
15899     GetTimeMark(&now);
15900
15901     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15902
15903     /* Fudge if we woke up a little too soon */
15904     fudge = intendedTickLength - lastTickLength;
15905     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15906
15907     if (WhiteOnMove(forwardMostMove)) {
15908         if(whiteNPS >= 0) lastTickLength = 0;
15909         timeRemaining = whiteTimeRemaining -= lastTickLength;
15910         if(timeRemaining < 0 && !appData.icsActive) {
15911             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15912             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15913                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15914                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15915             }
15916         }
15917         DisplayWhiteClock(whiteTimeRemaining - fudge,
15918                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15919     } else {
15920         if(blackNPS >= 0) lastTickLength = 0;
15921         timeRemaining = blackTimeRemaining -= lastTickLength;
15922         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15923             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15924             if(suddenDeath) {
15925                 blackStartMove = forwardMostMove;
15926                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15927             }
15928         }
15929         DisplayBlackClock(blackTimeRemaining - fudge,
15930                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15931     }
15932     if (CheckFlags()) return;
15933
15934     tickStartTM = now;
15935     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15936     StartClockTimer(intendedTickLength);
15937
15938     /* if the time remaining has fallen below the alarm threshold, sound the
15939      * alarm. if the alarm has sounded and (due to a takeback or time control
15940      * with increment) the time remaining has increased to a level above the
15941      * threshold, reset the alarm so it can sound again.
15942      */
15943
15944     if (appData.icsActive && appData.icsAlarm) {
15945
15946         /* make sure we are dealing with the user's clock */
15947         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15948                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15949            )) return;
15950
15951         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15952             alarmSounded = FALSE;
15953         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15954             PlayAlarmSound();
15955             alarmSounded = TRUE;
15956         }
15957     }
15958 }
15959
15960
15961 /* A player has just moved, so stop the previously running
15962    clock and (if in clock mode) start the other one.
15963    We redisplay both clocks in case we're in ICS mode, because
15964    ICS gives us an update to both clocks after every move.
15965    Note that this routine is called *after* forwardMostMove
15966    is updated, so the last fractional tick must be subtracted
15967    from the color that is *not* on move now.
15968 */
15969 void
15970 SwitchClocks (int newMoveNr)
15971 {
15972     long lastTickLength;
15973     TimeMark now;
15974     int flagged = FALSE;
15975
15976     GetTimeMark(&now);
15977
15978     if (StopClockTimer() && appData.clockMode) {
15979         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15980         if (!WhiteOnMove(forwardMostMove)) {
15981             if(blackNPS >= 0) lastTickLength = 0;
15982             blackTimeRemaining -= lastTickLength;
15983            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15984 //         if(pvInfoList[forwardMostMove].time == -1)
15985                  pvInfoList[forwardMostMove].time =               // use GUI time
15986                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15987         } else {
15988            if(whiteNPS >= 0) lastTickLength = 0;
15989            whiteTimeRemaining -= lastTickLength;
15990            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15991 //         if(pvInfoList[forwardMostMove].time == -1)
15992                  pvInfoList[forwardMostMove].time =
15993                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15994         }
15995         flagged = CheckFlags();
15996     }
15997     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15998     CheckTimeControl();
15999
16000     if (flagged || !appData.clockMode) return;
16001
16002     switch (gameMode) {
16003       case MachinePlaysBlack:
16004       case MachinePlaysWhite:
16005       case BeginningOfGame:
16006         if (pausing) return;
16007         break;
16008
16009       case EditGame:
16010       case PlayFromGameFile:
16011       case IcsExamining:
16012         return;
16013
16014       default:
16015         break;
16016     }
16017
16018     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16019         if(WhiteOnMove(forwardMostMove))
16020              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16021         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16022     }
16023
16024     tickStartTM = now;
16025     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16026       whiteTimeRemaining : blackTimeRemaining);
16027     StartClockTimer(intendedTickLength);
16028 }
16029
16030
16031 /* Stop both clocks */
16032 void
16033 StopClocks ()
16034 {
16035     long lastTickLength;
16036     TimeMark now;
16037
16038     if (!StopClockTimer()) return;
16039     if (!appData.clockMode) return;
16040
16041     GetTimeMark(&now);
16042
16043     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16044     if (WhiteOnMove(forwardMostMove)) {
16045         if(whiteNPS >= 0) lastTickLength = 0;
16046         whiteTimeRemaining -= lastTickLength;
16047         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16048     } else {
16049         if(blackNPS >= 0) lastTickLength = 0;
16050         blackTimeRemaining -= lastTickLength;
16051         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16052     }
16053     CheckFlags();
16054 }
16055
16056 /* Start clock of player on move.  Time may have been reset, so
16057    if clock is already running, stop and restart it. */
16058 void
16059 StartClocks ()
16060 {
16061     (void) StopClockTimer(); /* in case it was running already */
16062     DisplayBothClocks();
16063     if (CheckFlags()) return;
16064
16065     if (!appData.clockMode) return;
16066     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16067
16068     GetTimeMark(&tickStartTM);
16069     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16070       whiteTimeRemaining : blackTimeRemaining);
16071
16072    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16073     whiteNPS = blackNPS = -1;
16074     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16075        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16076         whiteNPS = first.nps;
16077     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16078        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16079         blackNPS = first.nps;
16080     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16081         whiteNPS = second.nps;
16082     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16083         blackNPS = second.nps;
16084     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16085
16086     StartClockTimer(intendedTickLength);
16087 }
16088
16089 char *
16090 TimeString (long ms)
16091 {
16092     long second, minute, hour, day;
16093     char *sign = "";
16094     static char buf[32];
16095
16096     if (ms > 0 && ms <= 9900) {
16097       /* convert milliseconds to tenths, rounding up */
16098       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16099
16100       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16101       return buf;
16102     }
16103
16104     /* convert milliseconds to seconds, rounding up */
16105     /* use floating point to avoid strangeness of integer division
16106        with negative dividends on many machines */
16107     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16108
16109     if (second < 0) {
16110         sign = "-";
16111         second = -second;
16112     }
16113
16114     day = second / (60 * 60 * 24);
16115     second = second % (60 * 60 * 24);
16116     hour = second / (60 * 60);
16117     second = second % (60 * 60);
16118     minute = second / 60;
16119     second = second % 60;
16120
16121     if (day > 0)
16122       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16123               sign, day, hour, minute, second);
16124     else if (hour > 0)
16125       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16126     else
16127       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16128
16129     return buf;
16130 }
16131
16132
16133 /*
16134  * This is necessary because some C libraries aren't ANSI C compliant yet.
16135  */
16136 char *
16137 StrStr (char *string, char *match)
16138 {
16139     int i, length;
16140
16141     length = strlen(match);
16142
16143     for (i = strlen(string) - length; i >= 0; i--, string++)
16144       if (!strncmp(match, string, length))
16145         return string;
16146
16147     return NULL;
16148 }
16149
16150 char *
16151 StrCaseStr (char *string, char *match)
16152 {
16153     int i, j, length;
16154
16155     length = strlen(match);
16156
16157     for (i = strlen(string) - length; i >= 0; i--, string++) {
16158         for (j = 0; j < length; j++) {
16159             if (ToLower(match[j]) != ToLower(string[j]))
16160               break;
16161         }
16162         if (j == length) return string;
16163     }
16164
16165     return NULL;
16166 }
16167
16168 #ifndef _amigados
16169 int
16170 StrCaseCmp (char *s1, char *s2)
16171 {
16172     char c1, c2;
16173
16174     for (;;) {
16175         c1 = ToLower(*s1++);
16176         c2 = ToLower(*s2++);
16177         if (c1 > c2) return 1;
16178         if (c1 < c2) return -1;
16179         if (c1 == NULLCHAR) return 0;
16180     }
16181 }
16182
16183
16184 int
16185 ToLower (int c)
16186 {
16187     return isupper(c) ? tolower(c) : c;
16188 }
16189
16190
16191 int
16192 ToUpper (int c)
16193 {
16194     return islower(c) ? toupper(c) : c;
16195 }
16196 #endif /* !_amigados    */
16197
16198 char *
16199 StrSave (char *s)
16200 {
16201   char *ret;
16202
16203   if ((ret = (char *) malloc(strlen(s) + 1)))
16204     {
16205       safeStrCpy(ret, s, strlen(s)+1);
16206     }
16207   return ret;
16208 }
16209
16210 char *
16211 StrSavePtr (char *s, char **savePtr)
16212 {
16213     if (*savePtr) {
16214         free(*savePtr);
16215     }
16216     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16217       safeStrCpy(*savePtr, s, strlen(s)+1);
16218     }
16219     return(*savePtr);
16220 }
16221
16222 char *
16223 PGNDate ()
16224 {
16225     time_t clock;
16226     struct tm *tm;
16227     char buf[MSG_SIZ];
16228
16229     clock = time((time_t *)NULL);
16230     tm = localtime(&clock);
16231     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16232             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16233     return StrSave(buf);
16234 }
16235
16236
16237 char *
16238 PositionToFEN (int move, char *overrideCastling)
16239 {
16240     int i, j, fromX, fromY, toX, toY;
16241     int whiteToPlay;
16242     char buf[MSG_SIZ];
16243     char *p, *q;
16244     int emptycount;
16245     ChessSquare piece;
16246
16247     whiteToPlay = (gameMode == EditPosition) ?
16248       !blackPlaysFirst : (move % 2 == 0);
16249     p = buf;
16250
16251     /* Piece placement data */
16252     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16253         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16254         emptycount = 0;
16255         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16256             if (boards[move][i][j] == EmptySquare) {
16257                 emptycount++;
16258             } else { ChessSquare piece = boards[move][i][j];
16259                 if (emptycount > 0) {
16260                     if(emptycount<10) /* [HGM] can be >= 10 */
16261                         *p++ = '0' + emptycount;
16262                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16263                     emptycount = 0;
16264                 }
16265                 if(PieceToChar(piece) == '+') {
16266                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16267                     *p++ = '+';
16268                     piece = (ChessSquare)(DEMOTED piece);
16269                 }
16270                 *p++ = PieceToChar(piece);
16271                 if(p[-1] == '~') {
16272                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16273                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16274                     *p++ = '~';
16275                 }
16276             }
16277         }
16278         if (emptycount > 0) {
16279             if(emptycount<10) /* [HGM] can be >= 10 */
16280                 *p++ = '0' + emptycount;
16281             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16282             emptycount = 0;
16283         }
16284         *p++ = '/';
16285     }
16286     *(p - 1) = ' ';
16287
16288     /* [HGM] print Crazyhouse or Shogi holdings */
16289     if( gameInfo.holdingsWidth ) {
16290         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16291         q = p;
16292         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16293             piece = boards[move][i][BOARD_WIDTH-1];
16294             if( piece != EmptySquare )
16295               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16296                   *p++ = PieceToChar(piece);
16297         }
16298         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16299             piece = boards[move][BOARD_HEIGHT-i-1][0];
16300             if( piece != EmptySquare )
16301               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16302                   *p++ = PieceToChar(piece);
16303         }
16304
16305         if( q == p ) *p++ = '-';
16306         *p++ = ']';
16307         *p++ = ' ';
16308     }
16309
16310     /* Active color */
16311     *p++ = whiteToPlay ? 'w' : 'b';
16312     *p++ = ' ';
16313
16314   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16315     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16316   } else {
16317   if(nrCastlingRights) {
16318      q = p;
16319      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16320        /* [HGM] write directly from rights */
16321            if(boards[move][CASTLING][2] != NoRights &&
16322               boards[move][CASTLING][0] != NoRights   )
16323                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16324            if(boards[move][CASTLING][2] != NoRights &&
16325               boards[move][CASTLING][1] != NoRights   )
16326                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16327            if(boards[move][CASTLING][5] != NoRights &&
16328               boards[move][CASTLING][3] != NoRights   )
16329                 *p++ = boards[move][CASTLING][3] + AAA;
16330            if(boards[move][CASTLING][5] != NoRights &&
16331               boards[move][CASTLING][4] != NoRights   )
16332                 *p++ = boards[move][CASTLING][4] + AAA;
16333      } else {
16334
16335         /* [HGM] write true castling rights */
16336         if( nrCastlingRights == 6 ) {
16337             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16338                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16339             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16340                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16341             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16342                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16343             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16344                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16345         }
16346      }
16347      if (q == p) *p++ = '-'; /* No castling rights */
16348      *p++ = ' ';
16349   }
16350
16351   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16352      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16353     /* En passant target square */
16354     if (move > backwardMostMove) {
16355         fromX = moveList[move - 1][0] - AAA;
16356         fromY = moveList[move - 1][1] - ONE;
16357         toX = moveList[move - 1][2] - AAA;
16358         toY = moveList[move - 1][3] - ONE;
16359         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16360             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16361             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16362             fromX == toX) {
16363             /* 2-square pawn move just happened */
16364             *p++ = toX + AAA;
16365             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16366         } else {
16367             *p++ = '-';
16368         }
16369     } else if(move == backwardMostMove) {
16370         // [HGM] perhaps we should always do it like this, and forget the above?
16371         if((signed char)boards[move][EP_STATUS] >= 0) {
16372             *p++ = boards[move][EP_STATUS] + AAA;
16373             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16374         } else {
16375             *p++ = '-';
16376         }
16377     } else {
16378         *p++ = '-';
16379     }
16380     *p++ = ' ';
16381   }
16382   }
16383
16384     /* [HGM] find reversible plies */
16385     {   int i = 0, j=move;
16386
16387         if (appData.debugMode) { int k;
16388             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16389             for(k=backwardMostMove; k<=forwardMostMove; k++)
16390                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16391
16392         }
16393
16394         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16395         if( j == backwardMostMove ) i += initialRulePlies;
16396         sprintf(p, "%d ", i);
16397         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16398     }
16399     /* Fullmove number */
16400     sprintf(p, "%d", (move / 2) + 1);
16401
16402     return StrSave(buf);
16403 }
16404
16405 Boolean
16406 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16407 {
16408     int i, j;
16409     char *p, c;
16410     int emptycount;
16411     ChessSquare piece;
16412
16413     p = fen;
16414
16415     /* [HGM] by default clear Crazyhouse holdings, if present */
16416     if(gameInfo.holdingsWidth) {
16417        for(i=0; i<BOARD_HEIGHT; i++) {
16418            board[i][0]             = EmptySquare; /* black holdings */
16419            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16420            board[i][1]             = (ChessSquare) 0; /* black counts */
16421            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16422        }
16423     }
16424
16425     /* Piece placement data */
16426     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16427         j = 0;
16428         for (;;) {
16429             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16430                 if (*p == '/') p++;
16431                 emptycount = gameInfo.boardWidth - j;
16432                 while (emptycount--)
16433                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16434                 break;
16435 #if(BOARD_FILES >= 10)
16436             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16437                 p++; emptycount=10;
16438                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16439                 while (emptycount--)
16440                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16441 #endif
16442             } else if (isdigit(*p)) {
16443                 emptycount = *p++ - '0';
16444                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16445                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16446                 while (emptycount--)
16447                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16448             } else if (*p == '+' || isalpha(*p)) {
16449                 if (j >= gameInfo.boardWidth) return FALSE;
16450                 if(*p=='+') {
16451                     piece = CharToPiece(*++p);
16452                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16453                     piece = (ChessSquare) (PROMOTED piece ); p++;
16454                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16455                 } else piece = CharToPiece(*p++);
16456
16457                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16458                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16459                     piece = (ChessSquare) (PROMOTED piece);
16460                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16461                     p++;
16462                 }
16463                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16464             } else {
16465                 return FALSE;
16466             }
16467         }
16468     }
16469     while (*p == '/' || *p == ' ') p++;
16470
16471     /* [HGM] look for Crazyhouse holdings here */
16472     while(*p==' ') p++;
16473     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16474         if(*p == '[') p++;
16475         if(*p == '-' ) p++; /* empty holdings */ else {
16476             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16477             /* if we would allow FEN reading to set board size, we would   */
16478             /* have to add holdings and shift the board read so far here   */
16479             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16480                 p++;
16481                 if((int) piece >= (int) BlackPawn ) {
16482                     i = (int)piece - (int)BlackPawn;
16483                     i = PieceToNumber((ChessSquare)i);
16484                     if( i >= gameInfo.holdingsSize ) return FALSE;
16485                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16486                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16487                 } else {
16488                     i = (int)piece - (int)WhitePawn;
16489                     i = PieceToNumber((ChessSquare)i);
16490                     if( i >= gameInfo.holdingsSize ) return FALSE;
16491                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16492                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16493                 }
16494             }
16495         }
16496         if(*p == ']') p++;
16497     }
16498
16499     while(*p == ' ') p++;
16500
16501     /* Active color */
16502     c = *p++;
16503     if(appData.colorNickNames) {
16504       if( c == appData.colorNickNames[0] ) c = 'w'; else
16505       if( c == appData.colorNickNames[1] ) c = 'b';
16506     }
16507     switch (c) {
16508       case 'w':
16509         *blackPlaysFirst = FALSE;
16510         break;
16511       case 'b':
16512         *blackPlaysFirst = TRUE;
16513         break;
16514       default:
16515         return FALSE;
16516     }
16517
16518     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16519     /* return the extra info in global variiables             */
16520
16521     /* set defaults in case FEN is incomplete */
16522     board[EP_STATUS] = EP_UNKNOWN;
16523     for(i=0; i<nrCastlingRights; i++ ) {
16524         board[CASTLING][i] =
16525             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16526     }   /* assume possible unless obviously impossible */
16527     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16528     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16529     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16530                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16531     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16532     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16533     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16534                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16535     FENrulePlies = 0;
16536
16537     while(*p==' ') p++;
16538     if(nrCastlingRights) {
16539       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16540           /* castling indicator present, so default becomes no castlings */
16541           for(i=0; i<nrCastlingRights; i++ ) {
16542                  board[CASTLING][i] = NoRights;
16543           }
16544       }
16545       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16546              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16547              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16548              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16549         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16550
16551         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16552             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16553             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16554         }
16555         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16556             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16557         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16558                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16559         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16560                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16561         switch(c) {
16562           case'K':
16563               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16564               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16565               board[CASTLING][2] = whiteKingFile;
16566               break;
16567           case'Q':
16568               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16569               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16570               board[CASTLING][2] = whiteKingFile;
16571               break;
16572           case'k':
16573               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16574               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16575               board[CASTLING][5] = blackKingFile;
16576               break;
16577           case'q':
16578               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16579               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16580               board[CASTLING][5] = blackKingFile;
16581           case '-':
16582               break;
16583           default: /* FRC castlings */
16584               if(c >= 'a') { /* black rights */
16585                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16586                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16587                   if(i == BOARD_RGHT) break;
16588                   board[CASTLING][5] = i;
16589                   c -= AAA;
16590                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16591                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16592                   if(c > i)
16593                       board[CASTLING][3] = c;
16594                   else
16595                       board[CASTLING][4] = c;
16596               } else { /* white rights */
16597                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16598                     if(board[0][i] == WhiteKing) break;
16599                   if(i == BOARD_RGHT) break;
16600                   board[CASTLING][2] = i;
16601                   c -= AAA - 'a' + 'A';
16602                   if(board[0][c] >= WhiteKing) break;
16603                   if(c > i)
16604                       board[CASTLING][0] = c;
16605                   else
16606                       board[CASTLING][1] = c;
16607               }
16608         }
16609       }
16610       for(i=0; i<nrCastlingRights; i++)
16611         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16612     if (appData.debugMode) {
16613         fprintf(debugFP, "FEN castling rights:");
16614         for(i=0; i<nrCastlingRights; i++)
16615         fprintf(debugFP, " %d", board[CASTLING][i]);
16616         fprintf(debugFP, "\n");
16617     }
16618
16619       while(*p==' ') p++;
16620     }
16621
16622     /* read e.p. field in games that know e.p. capture */
16623     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16624        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16625       if(*p=='-') {
16626         p++; board[EP_STATUS] = EP_NONE;
16627       } else {
16628          char c = *p++ - AAA;
16629
16630          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16631          if(*p >= '0' && *p <='9') p++;
16632          board[EP_STATUS] = c;
16633       }
16634     }
16635
16636
16637     if(sscanf(p, "%d", &i) == 1) {
16638         FENrulePlies = i; /* 50-move ply counter */
16639         /* (The move number is still ignored)    */
16640     }
16641
16642     return TRUE;
16643 }
16644
16645 void
16646 EditPositionPasteFEN (char *fen)
16647 {
16648   if (fen != NULL) {
16649     Board initial_position;
16650
16651     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16652       DisplayError(_("Bad FEN position in clipboard"), 0);
16653       return ;
16654     } else {
16655       int savedBlackPlaysFirst = blackPlaysFirst;
16656       EditPositionEvent();
16657       blackPlaysFirst = savedBlackPlaysFirst;
16658       CopyBoard(boards[0], initial_position);
16659       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16660       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16661       DisplayBothClocks();
16662       DrawPosition(FALSE, boards[currentMove]);
16663     }
16664   }
16665 }
16666
16667 static char cseq[12] = "\\   ";
16668
16669 Boolean
16670 set_cont_sequence (char *new_seq)
16671 {
16672     int len;
16673     Boolean ret;
16674
16675     // handle bad attempts to set the sequence
16676         if (!new_seq)
16677                 return 0; // acceptable error - no debug
16678
16679     len = strlen(new_seq);
16680     ret = (len > 0) && (len < sizeof(cseq));
16681     if (ret)
16682       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16683     else if (appData.debugMode)
16684       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16685     return ret;
16686 }
16687
16688 /*
16689     reformat a source message so words don't cross the width boundary.  internal
16690     newlines are not removed.  returns the wrapped size (no null character unless
16691     included in source message).  If dest is NULL, only calculate the size required
16692     for the dest buffer.  lp argument indicats line position upon entry, and it's
16693     passed back upon exit.
16694 */
16695 int
16696 wrap (char *dest, char *src, int count, int width, int *lp)
16697 {
16698     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16699
16700     cseq_len = strlen(cseq);
16701     old_line = line = *lp;
16702     ansi = len = clen = 0;
16703
16704     for (i=0; i < count; i++)
16705     {
16706         if (src[i] == '\033')
16707             ansi = 1;
16708
16709         // if we hit the width, back up
16710         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16711         {
16712             // store i & len in case the word is too long
16713             old_i = i, old_len = len;
16714
16715             // find the end of the last word
16716             while (i && src[i] != ' ' && src[i] != '\n')
16717             {
16718                 i--;
16719                 len--;
16720             }
16721
16722             // word too long?  restore i & len before splitting it
16723             if ((old_i-i+clen) >= width)
16724             {
16725                 i = old_i;
16726                 len = old_len;
16727             }
16728
16729             // extra space?
16730             if (i && src[i-1] == ' ')
16731                 len--;
16732
16733             if (src[i] != ' ' && src[i] != '\n')
16734             {
16735                 i--;
16736                 if (len)
16737                     len--;
16738             }
16739
16740             // now append the newline and continuation sequence
16741             if (dest)
16742                 dest[len] = '\n';
16743             len++;
16744             if (dest)
16745                 strncpy(dest+len, cseq, cseq_len);
16746             len += cseq_len;
16747             line = cseq_len;
16748             clen = cseq_len;
16749             continue;
16750         }
16751
16752         if (dest)
16753             dest[len] = src[i];
16754         len++;
16755         if (!ansi)
16756             line++;
16757         if (src[i] == '\n')
16758             line = 0;
16759         if (src[i] == 'm')
16760             ansi = 0;
16761     }
16762     if (dest && appData.debugMode)
16763     {
16764         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16765             count, width, line, len, *lp);
16766         show_bytes(debugFP, src, count);
16767         fprintf(debugFP, "\ndest: ");
16768         show_bytes(debugFP, dest, len);
16769         fprintf(debugFP, "\n");
16770     }
16771     *lp = dest ? line : old_line;
16772
16773     return len;
16774 }
16775
16776 // [HGM] vari: routines for shelving variations
16777 Boolean modeRestore = FALSE;
16778
16779 void
16780 PushInner (int firstMove, int lastMove)
16781 {
16782         int i, j, nrMoves = lastMove - firstMove;
16783
16784         // push current tail of game on stack
16785         savedResult[storedGames] = gameInfo.result;
16786         savedDetails[storedGames] = gameInfo.resultDetails;
16787         gameInfo.resultDetails = NULL;
16788         savedFirst[storedGames] = firstMove;
16789         savedLast [storedGames] = lastMove;
16790         savedFramePtr[storedGames] = framePtr;
16791         framePtr -= nrMoves; // reserve space for the boards
16792         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16793             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16794             for(j=0; j<MOVE_LEN; j++)
16795                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16796             for(j=0; j<2*MOVE_LEN; j++)
16797                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16798             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16799             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16800             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16801             pvInfoList[firstMove+i-1].depth = 0;
16802             commentList[framePtr+i] = commentList[firstMove+i];
16803             commentList[firstMove+i] = NULL;
16804         }
16805
16806         storedGames++;
16807         forwardMostMove = firstMove; // truncate game so we can start variation
16808 }
16809
16810 void
16811 PushTail (int firstMove, int lastMove)
16812 {
16813         if(appData.icsActive) { // only in local mode
16814                 forwardMostMove = currentMove; // mimic old ICS behavior
16815                 return;
16816         }
16817         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16818
16819         PushInner(firstMove, lastMove);
16820         if(storedGames == 1) GreyRevert(FALSE);
16821         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16822 }
16823
16824 void
16825 PopInner (Boolean annotate)
16826 {
16827         int i, j, nrMoves;
16828         char buf[8000], moveBuf[20];
16829
16830         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16831         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16832         nrMoves = savedLast[storedGames] - currentMove;
16833         if(annotate) {
16834                 int cnt = 10;
16835                 if(!WhiteOnMove(currentMove))
16836                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16837                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16838                 for(i=currentMove; i<forwardMostMove; i++) {
16839                         if(WhiteOnMove(i))
16840                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16841                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16842                         strcat(buf, moveBuf);
16843                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16844                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16845                 }
16846                 strcat(buf, ")");
16847         }
16848         for(i=1; i<=nrMoves; i++) { // copy last variation back
16849             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16850             for(j=0; j<MOVE_LEN; j++)
16851                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16852             for(j=0; j<2*MOVE_LEN; j++)
16853                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16854             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16855             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16856             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16857             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16858             commentList[currentMove+i] = commentList[framePtr+i];
16859             commentList[framePtr+i] = NULL;
16860         }
16861         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16862         framePtr = savedFramePtr[storedGames];
16863         gameInfo.result = savedResult[storedGames];
16864         if(gameInfo.resultDetails != NULL) {
16865             free(gameInfo.resultDetails);
16866       }
16867         gameInfo.resultDetails = savedDetails[storedGames];
16868         forwardMostMove = currentMove + nrMoves;
16869 }
16870
16871 Boolean
16872 PopTail (Boolean annotate)
16873 {
16874         if(appData.icsActive) return FALSE; // only in local mode
16875         if(!storedGames) return FALSE; // sanity
16876         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16877
16878         PopInner(annotate);
16879         if(currentMove < forwardMostMove) ForwardEvent(); else
16880         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16881
16882         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16883         return TRUE;
16884 }
16885
16886 void
16887 CleanupTail ()
16888 {       // remove all shelved variations
16889         int i;
16890         for(i=0; i<storedGames; i++) {
16891             if(savedDetails[i])
16892                 free(savedDetails[i]);
16893             savedDetails[i] = NULL;
16894         }
16895         for(i=framePtr; i<MAX_MOVES; i++) {
16896                 if(commentList[i]) free(commentList[i]);
16897                 commentList[i] = NULL;
16898         }
16899         framePtr = MAX_MOVES-1;
16900         storedGames = 0;
16901 }
16902
16903 void
16904 LoadVariation (int index, char *text)
16905 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16906         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16907         int level = 0, move;
16908
16909         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16910         // first find outermost bracketing variation
16911         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16912             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16913                 if(*p == '{') wait = '}'; else
16914                 if(*p == '[') wait = ']'; else
16915                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16916                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16917             }
16918             if(*p == wait) wait = NULLCHAR; // closing ]} found
16919             p++;
16920         }
16921         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16922         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16923         end[1] = NULLCHAR; // clip off comment beyond variation
16924         ToNrEvent(currentMove-1);
16925         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16926         // kludge: use ParsePV() to append variation to game
16927         move = currentMove;
16928         ParsePV(start, TRUE, TRUE);
16929         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16930         ClearPremoveHighlights();
16931         CommentPopDown();
16932         ToNrEvent(currentMove+1);
16933 }
16934