6d0708da92918b08c930fe5a9ff4289ea1be1d5d
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "gettext.h"
133
134 #ifdef ENABLE_NLS
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
138 #else
139 # ifdef WIN32
140 #   define _(s) T_(s)
141 #   define N_(s) s
142 # else
143 #   define _(s) (s)
144 #   define N_(s) s
145 #   define T_(s) s
146 # endif
147 #endif
148
149
150 int establish P((void));
151 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
152                          char *buf, int count, int error));
153 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
154                       char *buf, int count, int error));
155 void ics_printf P((char *format, ...));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225
226 #ifdef WIN32
227        extern void ConsoleCreate();
228 #endif
229
230 ChessProgramState *WhitePlayer();
231 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
232 int VerifyDisplayMode P(());
233
234 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
235 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
236 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
237 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
238 void ics_update_width P((int new_width));
239 extern char installDir[MSG_SIZ];
240 VariantClass startVariant; /* [HGM] nicks: initial variant */
241 Boolean abortMatch;
242
243 extern int tinyLayout, smallLayout;
244 ChessProgramStats programStats;
245 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
246 int endPV = -1;
247 static int exiting = 0; /* [HGM] moved to top */
248 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
249 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
250 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
251 int partnerHighlight[2];
252 Boolean partnerBoardValid = 0;
253 char partnerStatus[MSG_SIZ];
254 Boolean partnerUp;
255 Boolean originalFlip;
256 Boolean twoBoards = 0;
257 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
258 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
259 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
260 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
261 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
262 int opponentKibitzes;
263 int lastSavedGame; /* [HGM] save: ID of game */
264 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
265 extern int chatCount;
266 int chattingPartner;
267 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
268 char lastMsg[MSG_SIZ];
269 ChessSquare pieceSweep = EmptySquare;
270 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
271 int promoDefaultAltered;
272
273 /* States for ics_getting_history */
274 #define H_FALSE 0
275 #define H_REQUESTED 1
276 #define H_GOT_REQ_HEADER 2
277 #define H_GOT_UNREQ_HEADER 3
278 #define H_GETTING_MOVES 4
279 #define H_GOT_UNWANTED_HEADER 5
280
281 /* whosays values for GameEnds */
282 #define GE_ICS 0
283 #define GE_ENGINE 1
284 #define GE_PLAYER 2
285 #define GE_FILE 3
286 #define GE_XBOARD 4
287 #define GE_ENGINE1 5
288 #define GE_ENGINE2 6
289
290 /* Maximum number of games in a cmail message */
291 #define CMAIL_MAX_GAMES 20
292
293 /* Different types of move when calling RegisterMove */
294 #define CMAIL_MOVE   0
295 #define CMAIL_RESIGN 1
296 #define CMAIL_DRAW   2
297 #define CMAIL_ACCEPT 3
298
299 /* Different types of result to remember for each game */
300 #define CMAIL_NOT_RESULT 0
301 #define CMAIL_OLD_RESULT 1
302 #define CMAIL_NEW_RESULT 2
303
304 /* Telnet protocol constants */
305 #define TN_WILL 0373
306 #define TN_WONT 0374
307 #define TN_DO   0375
308 #define TN_DONT 0376
309 #define TN_IAC  0377
310 #define TN_ECHO 0001
311 #define TN_SGA  0003
312 #define TN_PORT 23
313
314 char*
315 safeStrCpy (char *dst, const char *src, size_t count)
316 { // [HGM] made safe
317   int i;
318   assert( dst != NULL );
319   assert( src != NULL );
320   assert( count > 0 );
321
322   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
323   if(  i == count && dst[count-1] != NULLCHAR)
324     {
325       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
326       if(appData.debugMode)
327       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
328     }
329
330   return dst;
331 }
332
333 /* Some compiler can't cast u64 to double
334  * This function do the job for us:
335
336  * We use the highest bit for cast, this only
337  * works if the highest bit is not
338  * in use (This should not happen)
339  *
340  * We used this for all compiler
341  */
342 double
343 u64ToDouble (u64 value)
344 {
345   double r;
346   u64 tmp = value & u64Const(0x7fffffffffffffff);
347   r = (double)(s64)tmp;
348   if (value & u64Const(0x8000000000000000))
349        r +=  9.2233720368547758080e18; /* 2^63 */
350  return r;
351 }
352
353 /* Fake up flags for now, as we aren't keeping track of castling
354    availability yet. [HGM] Change of logic: the flag now only
355    indicates the type of castlings allowed by the rule of the game.
356    The actual rights themselves are maintained in the array
357    castlingRights, as part of the game history, and are not probed
358    by this function.
359  */
360 int
361 PosFlags (index)
362 {
363   int flags = F_ALL_CASTLE_OK;
364   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
365   switch (gameInfo.variant) {
366   case VariantSuicide:
367     flags &= ~F_ALL_CASTLE_OK;
368   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
369     flags |= F_IGNORE_CHECK;
370   case VariantLosers:
371     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
372     break;
373   case VariantAtomic:
374     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
375     break;
376   case VariantKriegspiel:
377     flags |= F_KRIEGSPIEL_CAPTURE;
378     break;
379   case VariantCapaRandom:
380   case VariantFischeRandom:
381     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
382   case VariantNoCastle:
383   case VariantShatranj:
384   case VariantCourier:
385   case VariantMakruk:
386   case VariantGrand:
387     flags &= ~F_ALL_CASTLE_OK;
388     break;
389   default:
390     break;
391   }
392   return flags;
393 }
394
395 FILE *gameFileFP, *debugFP;
396
397 /*
398     [AS] Note: sometimes, the sscanf() function is used to parse the input
399     into a fixed-size buffer. Because of this, we must be prepared to
400     receive strings as long as the size of the input buffer, which is currently
401     set to 4K for Windows and 8K for the rest.
402     So, we must either allocate sufficiently large buffers here, or
403     reduce the size of the input buffer in the input reading part.
404 */
405
406 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
407 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
408 char thinkOutput1[MSG_SIZ*10];
409
410 ChessProgramState first, second, pairing;
411
412 /* premove variables */
413 int premoveToX = 0;
414 int premoveToY = 0;
415 int premoveFromX = 0;
416 int premoveFromY = 0;
417 int premovePromoChar = 0;
418 int gotPremove = 0;
419 Boolean alarmSounded;
420 /* end premove variables */
421
422 char *ics_prefix = "$";
423 int ics_type = ICS_GENERIC;
424
425 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
426 int pauseExamForwardMostMove = 0;
427 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
428 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
429 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
430 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
431 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
432 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
433 int whiteFlag = FALSE, blackFlag = FALSE;
434 int userOfferedDraw = FALSE;
435 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
436 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
437 int cmailMoveType[CMAIL_MAX_GAMES];
438 long ics_clock_paused = 0;
439 ProcRef icsPR = NoProc, cmailPR = NoProc;
440 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
441 GameMode gameMode = BeginningOfGame;
442 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
443 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
444 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
445 int hiddenThinkOutputState = 0; /* [AS] */
446 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
447 int adjudicateLossPlies = 6;
448 char white_holding[64], black_holding[64];
449 TimeMark lastNodeCountTime;
450 long lastNodeCount=0;
451 int shiftKey; // [HGM] set by mouse handler
452
453 int have_sent_ICS_logon = 0;
454 int movesPerSession;
455 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
456 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
457 Boolean adjustedClock;
458 long timeControl_2; /* [AS] Allow separate time controls */
459 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
460 long timeRemaining[2][MAX_MOVES];
461 int matchGame = 0, nextGame = 0, roundNr = 0;
462 Boolean waitingForGame = FALSE;
463 TimeMark programStartTime, pauseStart;
464 char ics_handle[MSG_SIZ];
465 int have_set_title = 0;
466
467 /* animateTraining preserves the state of appData.animate
468  * when Training mode is activated. This allows the
469  * response to be animated when appData.animate == TRUE and
470  * appData.animateDragging == TRUE.
471  */
472 Boolean animateTraining;
473
474 GameInfo gameInfo;
475
476 AppData appData;
477
478 Board boards[MAX_MOVES];
479 /* [HGM] Following 7 needed for accurate legality tests: */
480 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
481 signed char  initialRights[BOARD_FILES];
482 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
483 int   initialRulePlies, FENrulePlies;
484 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
485 int loadFlag = 0;
486 Boolean shuffleOpenings;
487 int mute; // mute all sounds
488
489 // [HGM] vari: next 12 to save and restore variations
490 #define MAX_VARIATIONS 10
491 int framePtr = MAX_MOVES-1; // points to free stack entry
492 int storedGames = 0;
493 int savedFirst[MAX_VARIATIONS];
494 int savedLast[MAX_VARIATIONS];
495 int savedFramePtr[MAX_VARIATIONS];
496 char *savedDetails[MAX_VARIATIONS];
497 ChessMove savedResult[MAX_VARIATIONS];
498
499 void PushTail P((int firstMove, int lastMove));
500 Boolean PopTail P((Boolean annotate));
501 void PushInner P((int firstMove, int lastMove));
502 void PopInner P((Boolean annotate));
503 void CleanupTail P((void));
504
505 ChessSquare  FIDEArray[2][BOARD_FILES] = {
506     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
507         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
508     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
509         BlackKing, BlackBishop, BlackKnight, BlackRook }
510 };
511
512 ChessSquare twoKingsArray[2][BOARD_FILES] = {
513     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
514         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
515     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
516         BlackKing, BlackKing, BlackKnight, BlackRook }
517 };
518
519 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
520     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
521         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
522     { BlackRook, BlackMan, BlackBishop, BlackQueen,
523         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
524 };
525
526 ChessSquare SpartanArray[2][BOARD_FILES] = {
527     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
528         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
529     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
530         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
531 };
532
533 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
534     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
535         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
536     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
537         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
538 };
539
540 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
541     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
542         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
544         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
545 };
546
547 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
548     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
549         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
550     { BlackRook, BlackKnight, BlackMan, BlackFerz,
551         BlackKing, BlackMan, BlackKnight, BlackRook }
552 };
553
554
555 #if (BOARD_FILES>=10)
556 ChessSquare ShogiArray[2][BOARD_FILES] = {
557     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
558         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
559     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
560         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
561 };
562
563 ChessSquare XiangqiArray[2][BOARD_FILES] = {
564     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
565         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
566     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
567         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
568 };
569
570 ChessSquare CapablancaArray[2][BOARD_FILES] = {
571     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
572         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
573     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
574         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
575 };
576
577 ChessSquare GreatArray[2][BOARD_FILES] = {
578     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
579         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
580     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
581         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
582 };
583
584 ChessSquare JanusArray[2][BOARD_FILES] = {
585     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
586         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
587     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
588         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
589 };
590
591 ChessSquare GrandArray[2][BOARD_FILES] = {
592     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
593         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
594     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
595         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
596 };
597
598 #ifdef GOTHIC
599 ChessSquare GothicArray[2][BOARD_FILES] = {
600     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
601         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
602     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
603         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
604 };
605 #else // !GOTHIC
606 #define GothicArray CapablancaArray
607 #endif // !GOTHIC
608
609 #ifdef FALCON
610 ChessSquare FalconArray[2][BOARD_FILES] = {
611     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
612         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
613     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
614         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
615 };
616 #else // !FALCON
617 #define FalconArray CapablancaArray
618 #endif // !FALCON
619
620 #else // !(BOARD_FILES>=10)
621 #define XiangqiPosition FIDEArray
622 #define CapablancaArray FIDEArray
623 #define GothicArray FIDEArray
624 #define GreatArray FIDEArray
625 #endif // !(BOARD_FILES>=10)
626
627 #if (BOARD_FILES>=12)
628 ChessSquare CourierArray[2][BOARD_FILES] = {
629     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
630         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
631     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
632         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
633 };
634 #else // !(BOARD_FILES>=12)
635 #define CourierArray CapablancaArray
636 #endif // !(BOARD_FILES>=12)
637
638
639 Board initialPosition;
640
641
642 /* Convert str to a rating. Checks for special cases of "----",
643
644    "++++", etc. Also strips ()'s */
645 int
646 string_to_rating (char *str)
647 {
648   while(*str && !isdigit(*str)) ++str;
649   if (!*str)
650     return 0;   /* One of the special "no rating" cases */
651   else
652     return atoi(str);
653 }
654
655 void
656 ClearProgramStats ()
657 {
658     /* Init programStats */
659     programStats.movelist[0] = 0;
660     programStats.depth = 0;
661     programStats.nr_moves = 0;
662     programStats.moves_left = 0;
663     programStats.nodes = 0;
664     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
665     programStats.score = 0;
666     programStats.got_only_move = 0;
667     programStats.got_fail = 0;
668     programStats.line_is_book = 0;
669 }
670
671 void
672 CommonEngineInit ()
673 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
674     if (appData.firstPlaysBlack) {
675         first.twoMachinesColor = "black\n";
676         second.twoMachinesColor = "white\n";
677     } else {
678         first.twoMachinesColor = "white\n";
679         second.twoMachinesColor = "black\n";
680     }
681
682     first.other = &second;
683     second.other = &first;
684
685     { float norm = 1;
686         if(appData.timeOddsMode) {
687             norm = appData.timeOdds[0];
688             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
689         }
690         first.timeOdds  = appData.timeOdds[0]/norm;
691         second.timeOdds = appData.timeOdds[1]/norm;
692     }
693
694     if(programVersion) free(programVersion);
695     if (appData.noChessProgram) {
696         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
697         sprintf(programVersion, "%s", PACKAGE_STRING);
698     } else {
699       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
700       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
701       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
702     }
703 }
704
705 void
706 UnloadEngine (ChessProgramState *cps)
707 {
708         /* Kill off first chess program */
709         if (cps->isr != NULL)
710           RemoveInputSource(cps->isr);
711         cps->isr = NULL;
712
713         if (cps->pr != NoProc) {
714             ExitAnalyzeMode();
715             DoSleep( appData.delayBeforeQuit );
716             SendToProgram("quit\n", cps);
717             DoSleep( appData.delayAfterQuit );
718             DestroyChildProcess(cps->pr, cps->useSigterm);
719         }
720         cps->pr = NoProc;
721         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
722 }
723
724 void
725 ClearOptions (ChessProgramState *cps)
726 {
727     int i;
728     cps->nrOptions = cps->comboCnt = 0;
729     for(i=0; i<MAX_OPTIONS; i++) {
730         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
731         cps->option[i].textValue = 0;
732     }
733 }
734
735 char *engineNames[] = {
736 "first",
737 "second"
738 };
739
740 void
741 InitEngine (ChessProgramState *cps, int n)
742 {   // [HGM] all engine initialiation put in a function that does one engine
743
744     ClearOptions(cps);
745
746     cps->which = engineNames[n];
747     cps->maybeThinking = FALSE;
748     cps->pr = NoProc;
749     cps->isr = NULL;
750     cps->sendTime = 2;
751     cps->sendDrawOffers = 1;
752
753     cps->program = appData.chessProgram[n];
754     cps->host = appData.host[n];
755     cps->dir = appData.directory[n];
756     cps->initString = appData.engInitString[n];
757     cps->computerString = appData.computerString[n];
758     cps->useSigint  = TRUE;
759     cps->useSigterm = TRUE;
760     cps->reuse = appData.reuse[n];
761     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
762     cps->useSetboard = FALSE;
763     cps->useSAN = FALSE;
764     cps->usePing = FALSE;
765     cps->lastPing = 0;
766     cps->lastPong = 0;
767     cps->usePlayother = FALSE;
768     cps->useColors = TRUE;
769     cps->useUsermove = FALSE;
770     cps->sendICS = FALSE;
771     cps->sendName = appData.icsActive;
772     cps->sdKludge = FALSE;
773     cps->stKludge = FALSE;
774     TidyProgramName(cps->program, cps->host, cps->tidy);
775     cps->matchWins = 0;
776     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
777     cps->analysisSupport = 2; /* detect */
778     cps->analyzing = FALSE;
779     cps->initDone = FALSE;
780
781     /* New features added by Tord: */
782     cps->useFEN960 = FALSE;
783     cps->useOOCastle = TRUE;
784     /* End of new features added by Tord. */
785     cps->fenOverride  = appData.fenOverride[n];
786
787     /* [HGM] time odds: set factor for each machine */
788     cps->timeOdds  = appData.timeOdds[n];
789
790     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
791     cps->accumulateTC = appData.accumulateTC[n];
792     cps->maxNrOfSessions = 1;
793
794     /* [HGM] debug */
795     cps->debug = FALSE;
796
797     cps->supportsNPS = UNKNOWN;
798     cps->memSize = FALSE;
799     cps->maxCores = FALSE;
800     cps->egtFormats[0] = NULLCHAR;
801
802     /* [HGM] options */
803     cps->optionSettings  = appData.engOptions[n];
804
805     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
806     cps->isUCI = appData.isUCI[n]; /* [AS] */
807     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
808
809     if (appData.protocolVersion[n] > PROTOVER
810         || appData.protocolVersion[n] < 1)
811       {
812         char buf[MSG_SIZ];
813         int len;
814
815         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
816                        appData.protocolVersion[n]);
817         if( (len >= MSG_SIZ) && appData.debugMode )
818           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
819
820         DisplayFatalError(buf, 0, 2);
821       }
822     else
823       {
824         cps->protocolVersion = appData.protocolVersion[n];
825       }
826
827     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
828     ParseFeatures(appData.featureDefaults, cps);
829 }
830
831 ChessProgramState *savCps;
832
833 void
834 LoadEngine ()
835 {
836     int i;
837     if(WaitForEngine(savCps, LoadEngine)) return;
838     CommonEngineInit(); // recalculate time odds
839     if(gameInfo.variant != StringToVariant(appData.variant)) {
840         // we changed variant when loading the engine; this forces us to reset
841         Reset(TRUE, savCps != &first);
842         EditGameEvent(); // for consistency with other path, as Reset changes mode
843     }
844     InitChessProgram(savCps, FALSE);
845     SendToProgram("force\n", savCps);
846     DisplayMessage("", "");
847     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
848     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
849     ThawUI();
850     SetGNUMode();
851 }
852
853 void
854 ReplaceEngine (ChessProgramState *cps, int n)
855 {
856     EditGameEvent();
857     UnloadEngine(cps);
858     appData.noChessProgram = FALSE;
859     appData.clockMode = TRUE;
860     InitEngine(cps, n);
861     UpdateLogos(TRUE);
862     if(n) return; // only startup first engine immediately; second can wait
863     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
864     LoadEngine();
865 }
866
867 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
868 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
869
870 static char resetOptions[] = 
871         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
872         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
873         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
874
875 void
876 Load (ChessProgramState *cps, int i)
877 {
878     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
879     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
880         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
881         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
882         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
883         ParseArgsFromString(buf);
884         SwapEngines(i);
885         ReplaceEngine(cps, i);
886         return;
887     }
888     p = engineName;
889     while(q = strchr(p, SLASH)) p = q+1;
890     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
891     if(engineDir[0] != NULLCHAR)
892         appData.directory[i] = engineDir;
893     else if(p != engineName) { // derive directory from engine path, when not given
894         p[-1] = 0;
895         appData.directory[i] = strdup(engineName);
896         p[-1] = SLASH;
897     } else appData.directory[i] = ".";
898     if(params[0]) {
899         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
900         snprintf(command, MSG_SIZ, "%s %s", p, params);
901         p = command;
902     }
903     appData.chessProgram[i] = strdup(p);
904     appData.isUCI[i] = isUCI;
905     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
906     appData.hasOwnBookUCI[i] = hasBook;
907     if(!nickName[0]) useNick = FALSE;
908     if(useNick) ASSIGN(appData.pgnName[i], nickName);
909     if(addToList) {
910         int len;
911         char quote;
912         q = firstChessProgramNames;
913         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
914         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
915         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
916                         quote, p, quote, appData.directory[i], 
917                         useNick ? " -fn \"" : "",
918                         useNick ? nickName : "",
919                         useNick ? "\"" : "",
920                         v1 ? " -firstProtocolVersion 1" : "",
921                         hasBook ? "" : " -fNoOwnBookUCI",
922                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
923                         storeVariant ? " -variant " : "",
924                         storeVariant ? VariantName(gameInfo.variant) : "");
925         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
926         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
927         if(q)   free(q);
928     }
929     ReplaceEngine(cps, i);
930 }
931
932 void
933 InitTimeControls ()
934 {
935     int matched, min, sec;
936     /*
937      * Parse timeControl resource
938      */
939     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
940                           appData.movesPerSession)) {
941         char buf[MSG_SIZ];
942         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
943         DisplayFatalError(buf, 0, 2);
944     }
945
946     /*
947      * Parse searchTime resource
948      */
949     if (*appData.searchTime != NULLCHAR) {
950         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
951         if (matched == 1) {
952             searchTime = min * 60;
953         } else if (matched == 2) {
954             searchTime = min * 60 + sec;
955         } else {
956             char buf[MSG_SIZ];
957             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
958             DisplayFatalError(buf, 0, 2);
959         }
960     }
961 }
962
963 void
964 InitBackEnd1 ()
965 {
966
967     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
968     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
969
970     GetTimeMark(&programStartTime);
971     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
972     appData.seedBase = random() + (random()<<15);
973     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
974
975     ClearProgramStats();
976     programStats.ok_to_send = 1;
977     programStats.seen_stat = 0;
978
979     /*
980      * Initialize game list
981      */
982     ListNew(&gameList);
983
984
985     /*
986      * Internet chess server status
987      */
988     if (appData.icsActive) {
989         appData.matchMode = FALSE;
990         appData.matchGames = 0;
991 #if ZIPPY
992         appData.noChessProgram = !appData.zippyPlay;
993 #else
994         appData.zippyPlay = FALSE;
995         appData.zippyTalk = FALSE;
996         appData.noChessProgram = TRUE;
997 #endif
998         if (*appData.icsHelper != NULLCHAR) {
999             appData.useTelnet = TRUE;
1000             appData.telnetProgram = appData.icsHelper;
1001         }
1002     } else {
1003         appData.zippyTalk = appData.zippyPlay = FALSE;
1004     }
1005
1006     /* [AS] Initialize pv info list [HGM] and game state */
1007     {
1008         int i, j;
1009
1010         for( i=0; i<=framePtr; i++ ) {
1011             pvInfoList[i].depth = -1;
1012             boards[i][EP_STATUS] = EP_NONE;
1013             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1014         }
1015     }
1016
1017     InitTimeControls();
1018
1019     /* [AS] Adjudication threshold */
1020     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1021
1022     InitEngine(&first, 0);
1023     InitEngine(&second, 1);
1024     CommonEngineInit();
1025
1026     pairing.which = "pairing"; // pairing engine
1027     pairing.pr = NoProc;
1028     pairing.isr = NULL;
1029     pairing.program = appData.pairingEngine;
1030     pairing.host = "localhost";
1031     pairing.dir = ".";
1032
1033     if (appData.icsActive) {
1034         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1035     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1036         appData.clockMode = FALSE;
1037         first.sendTime = second.sendTime = 0;
1038     }
1039
1040 #if ZIPPY
1041     /* Override some settings from environment variables, for backward
1042        compatibility.  Unfortunately it's not feasible to have the env
1043        vars just set defaults, at least in xboard.  Ugh.
1044     */
1045     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1046       ZippyInit();
1047     }
1048 #endif
1049
1050     if (!appData.icsActive) {
1051       char buf[MSG_SIZ];
1052       int len;
1053
1054       /* Check for variants that are supported only in ICS mode,
1055          or not at all.  Some that are accepted here nevertheless
1056          have bugs; see comments below.
1057       */
1058       VariantClass variant = StringToVariant(appData.variant);
1059       switch (variant) {
1060       case VariantBughouse:     /* need four players and two boards */
1061       case VariantKriegspiel:   /* need to hide pieces and move details */
1062         /* case VariantFischeRandom: (Fabien: moved below) */
1063         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1064         if( (len >= MSG_SIZ) && appData.debugMode )
1065           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1066
1067         DisplayFatalError(buf, 0, 2);
1068         return;
1069
1070       case VariantUnknown:
1071       case VariantLoadable:
1072       case Variant29:
1073       case Variant30:
1074       case Variant31:
1075       case Variant32:
1076       case Variant33:
1077       case Variant34:
1078       case Variant35:
1079       case Variant36:
1080       default:
1081         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1082         if( (len >= MSG_SIZ) && appData.debugMode )
1083           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1084
1085         DisplayFatalError(buf, 0, 2);
1086         return;
1087
1088       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1089       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1090       case VariantGothic:     /* [HGM] should work */
1091       case VariantCapablanca: /* [HGM] should work */
1092       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1093       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1094       case VariantKnightmate: /* [HGM] should work */
1095       case VariantCylinder:   /* [HGM] untested */
1096       case VariantFalcon:     /* [HGM] untested */
1097       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1098                                  offboard interposition not understood */
1099       case VariantNormal:     /* definitely works! */
1100       case VariantWildCastle: /* pieces not automatically shuffled */
1101       case VariantNoCastle:   /* pieces not automatically shuffled */
1102       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1103       case VariantLosers:     /* should work except for win condition,
1104                                  and doesn't know captures are mandatory */
1105       case VariantSuicide:    /* should work except for win condition,
1106                                  and doesn't know captures are mandatory */
1107       case VariantGiveaway:   /* should work except for win condition,
1108                                  and doesn't know captures are mandatory */
1109       case VariantTwoKings:   /* should work */
1110       case VariantAtomic:     /* should work except for win condition */
1111       case Variant3Check:     /* should work except for win condition */
1112       case VariantShatranj:   /* should work except for all win conditions */
1113       case VariantMakruk:     /* should work except for draw countdown */
1114       case VariantBerolina:   /* might work if TestLegality is off */
1115       case VariantCapaRandom: /* should work */
1116       case VariantJanus:      /* should work */
1117       case VariantSuper:      /* experimental */
1118       case VariantGreat:      /* experimental, requires legality testing to be off */
1119       case VariantSChess:     /* S-Chess, should work */
1120       case VariantGrand:      /* should work */
1121       case VariantSpartan:    /* should work */
1122         break;
1123       }
1124     }
1125
1126 }
1127
1128 int
1129 NextIntegerFromString (char ** str, long * value)
1130 {
1131     int result = -1;
1132     char * s = *str;
1133
1134     while( *s == ' ' || *s == '\t' ) {
1135         s++;
1136     }
1137
1138     *value = 0;
1139
1140     if( *s >= '0' && *s <= '9' ) {
1141         while( *s >= '0' && *s <= '9' ) {
1142             *value = *value * 10 + (*s - '0');
1143             s++;
1144         }
1145
1146         result = 0;
1147     }
1148
1149     *str = s;
1150
1151     return result;
1152 }
1153
1154 int
1155 NextTimeControlFromString (char ** str, long * value)
1156 {
1157     long temp;
1158     int result = NextIntegerFromString( str, &temp );
1159
1160     if( result == 0 ) {
1161         *value = temp * 60; /* Minutes */
1162         if( **str == ':' ) {
1163             (*str)++;
1164             result = NextIntegerFromString( str, &temp );
1165             *value += temp; /* Seconds */
1166         }
1167     }
1168
1169     return result;
1170 }
1171
1172 int
1173 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1174 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1175     int result = -1, type = 0; long temp, temp2;
1176
1177     if(**str != ':') return -1; // old params remain in force!
1178     (*str)++;
1179     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1180     if( NextIntegerFromString( str, &temp ) ) return -1;
1181     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1182
1183     if(**str != '/') {
1184         /* time only: incremental or sudden-death time control */
1185         if(**str == '+') { /* increment follows; read it */
1186             (*str)++;
1187             if(**str == '!') type = *(*str)++; // Bronstein TC
1188             if(result = NextIntegerFromString( str, &temp2)) return -1;
1189             *inc = temp2 * 1000;
1190             if(**str == '.') { // read fraction of increment
1191                 char *start = ++(*str);
1192                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1193                 temp2 *= 1000;
1194                 while(start++ < *str) temp2 /= 10;
1195                 *inc += temp2;
1196             }
1197         } else *inc = 0;
1198         *moves = 0; *tc = temp * 1000; *incType = type;
1199         return 0;
1200     }
1201
1202     (*str)++; /* classical time control */
1203     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1204
1205     if(result == 0) {
1206         *moves = temp;
1207         *tc    = temp2 * 1000;
1208         *inc   = 0;
1209         *incType = type;
1210     }
1211     return result;
1212 }
1213
1214 int
1215 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1216 {   /* [HGM] get time to add from the multi-session time-control string */
1217     int incType, moves=1; /* kludge to force reading of first session */
1218     long time, increment;
1219     char *s = tcString;
1220
1221     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1222     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1223     do {
1224         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1225         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1226         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1227         if(movenr == -1) return time;    /* last move before new session     */
1228         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1229         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1230         if(!moves) return increment;     /* current session is incremental   */
1231         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1232     } while(movenr >= -1);               /* try again for next session       */
1233
1234     return 0; // no new time quota on this move
1235 }
1236
1237 int
1238 ParseTimeControl (char *tc, float ti, int mps)
1239 {
1240   long tc1;
1241   long tc2;
1242   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1243   int min, sec=0;
1244
1245   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1246   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1247       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1248   if(ti > 0) {
1249
1250     if(mps)
1251       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1252     else 
1253       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1254   } else {
1255     if(mps)
1256       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1257     else 
1258       snprintf(buf, MSG_SIZ, ":%s", mytc);
1259   }
1260   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1261   
1262   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1263     return FALSE;
1264   }
1265
1266   if( *tc == '/' ) {
1267     /* Parse second time control */
1268     tc++;
1269
1270     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1271       return FALSE;
1272     }
1273
1274     if( tc2 == 0 ) {
1275       return FALSE;
1276     }
1277
1278     timeControl_2 = tc2 * 1000;
1279   }
1280   else {
1281     timeControl_2 = 0;
1282   }
1283
1284   if( tc1 == 0 ) {
1285     return FALSE;
1286   }
1287
1288   timeControl = tc1 * 1000;
1289
1290   if (ti >= 0) {
1291     timeIncrement = ti * 1000;  /* convert to ms */
1292     movesPerSession = 0;
1293   } else {
1294     timeIncrement = 0;
1295     movesPerSession = mps;
1296   }
1297   return TRUE;
1298 }
1299
1300 void
1301 InitBackEnd2 ()
1302 {
1303     if (appData.debugMode) {
1304         fprintf(debugFP, "%s\n", programVersion);
1305     }
1306
1307     set_cont_sequence(appData.wrapContSeq);
1308     if (appData.matchGames > 0) {
1309         appData.matchMode = TRUE;
1310     } else if (appData.matchMode) {
1311         appData.matchGames = 1;
1312     }
1313     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1314         appData.matchGames = appData.sameColorGames;
1315     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1316         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1317         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1318     }
1319     Reset(TRUE, FALSE);
1320     if (appData.noChessProgram || first.protocolVersion == 1) {
1321       InitBackEnd3();
1322     } else {
1323       /* kludge: allow timeout for initial "feature" commands */
1324       FreezeUI();
1325       DisplayMessage("", _("Starting chess program"));
1326       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1327     }
1328 }
1329
1330 int
1331 CalculateIndex (int index, int gameNr)
1332 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1333     int res;
1334     if(index > 0) return index; // fixed nmber
1335     if(index == 0) return 1;
1336     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1337     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1338     return res;
1339 }
1340
1341 int
1342 LoadGameOrPosition (int gameNr)
1343 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1344     if (*appData.loadGameFile != NULLCHAR) {
1345         if (!LoadGameFromFile(appData.loadGameFile,
1346                 CalculateIndex(appData.loadGameIndex, gameNr),
1347                               appData.loadGameFile, FALSE)) {
1348             DisplayFatalError(_("Bad game file"), 0, 1);
1349             return 0;
1350         }
1351     } else if (*appData.loadPositionFile != NULLCHAR) {
1352         if (!LoadPositionFromFile(appData.loadPositionFile,
1353                 CalculateIndex(appData.loadPositionIndex, gameNr),
1354                                   appData.loadPositionFile)) {
1355             DisplayFatalError(_("Bad position file"), 0, 1);
1356             return 0;
1357         }
1358     }
1359     return 1;
1360 }
1361
1362 void
1363 ReserveGame (int gameNr, char resChar)
1364 {
1365     FILE *tf = fopen(appData.tourneyFile, "r+");
1366     char *p, *q, c, buf[MSG_SIZ];
1367     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1368     safeStrCpy(buf, lastMsg, MSG_SIZ);
1369     DisplayMessage(_("Pick new game"), "");
1370     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1371     ParseArgsFromFile(tf);
1372     p = q = appData.results;
1373     if(appData.debugMode) {
1374       char *r = appData.participants;
1375       fprintf(debugFP, "results = '%s'\n", p);
1376       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1377       fprintf(debugFP, "\n");
1378     }
1379     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1380     nextGame = q - p;
1381     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1382     safeStrCpy(q, p, strlen(p) + 2);
1383     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1384     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1385     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1386         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1387         q[nextGame] = '*';
1388     }
1389     fseek(tf, -(strlen(p)+4), SEEK_END);
1390     c = fgetc(tf);
1391     if(c != '"') // depending on DOS or Unix line endings we can be one off
1392          fseek(tf, -(strlen(p)+2), SEEK_END);
1393     else fseek(tf, -(strlen(p)+3), SEEK_END);
1394     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1395     DisplayMessage(buf, "");
1396     free(p); appData.results = q;
1397     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1398        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1399         UnloadEngine(&first);  // next game belongs to other pairing;
1400         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1401     }
1402 }
1403
1404 void
1405 MatchEvent (int mode)
1406 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1407         int dummy;
1408         if(matchMode) { // already in match mode: switch it off
1409             abortMatch = TRUE;
1410             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1411             return;
1412         }
1413 //      if(gameMode != BeginningOfGame) {
1414 //          DisplayError(_("You can only start a match from the initial position."), 0);
1415 //          return;
1416 //      }
1417         abortMatch = FALSE;
1418         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1419         /* Set up machine vs. machine match */
1420         nextGame = 0;
1421         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1422         if(appData.tourneyFile[0]) {
1423             ReserveGame(-1, 0);
1424             if(nextGame > appData.matchGames) {
1425                 char buf[MSG_SIZ];
1426                 if(strchr(appData.results, '*') == NULL) {
1427                     FILE *f;
1428                     appData.tourneyCycles++;
1429                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1430                         fclose(f);
1431                         NextTourneyGame(-1, &dummy);
1432                         ReserveGame(-1, 0);
1433                         if(nextGame <= appData.matchGames) {
1434                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1435                             matchMode = mode;
1436                             ScheduleDelayedEvent(NextMatchGame, 10000);
1437                             return;
1438                         }
1439                     }
1440                 }
1441                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1442                 DisplayError(buf, 0);
1443                 appData.tourneyFile[0] = 0;
1444                 return;
1445             }
1446         } else
1447         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1448             DisplayFatalError(_("Can't have a match with no chess programs"),
1449                               0, 2);
1450             return;
1451         }
1452         matchMode = mode;
1453         matchGame = roundNr = 1;
1454         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1455         NextMatchGame();
1456 }
1457
1458 void
1459 InitBackEnd3 P((void))
1460 {
1461     GameMode initialMode;
1462     char buf[MSG_SIZ];
1463     int err, len;
1464
1465     InitChessProgram(&first, startedFromSetupPosition);
1466
1467     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1468         free(programVersion);
1469         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1470         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1471     }
1472
1473     if (appData.icsActive) {
1474 #ifdef WIN32
1475         /* [DM] Make a console window if needed [HGM] merged ifs */
1476         ConsoleCreate();
1477 #endif
1478         err = establish();
1479         if (err != 0)
1480           {
1481             if (*appData.icsCommPort != NULLCHAR)
1482               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1483                              appData.icsCommPort);
1484             else
1485               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1486                         appData.icsHost, appData.icsPort);
1487
1488             if( (len >= MSG_SIZ) && appData.debugMode )
1489               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1490
1491             DisplayFatalError(buf, err, 1);
1492             return;
1493         }
1494         SetICSMode();
1495         telnetISR =
1496           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1497         fromUserISR =
1498           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1499         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1500             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1501     } else if (appData.noChessProgram) {
1502         SetNCPMode();
1503     } else {
1504         SetGNUMode();
1505     }
1506
1507     if (*appData.cmailGameName != NULLCHAR) {
1508         SetCmailMode();
1509         OpenLoopback(&cmailPR);
1510         cmailISR =
1511           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1512     }
1513
1514     ThawUI();
1515     DisplayMessage("", "");
1516     if (StrCaseCmp(appData.initialMode, "") == 0) {
1517       initialMode = BeginningOfGame;
1518       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1519         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1520         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1521         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1522         ModeHighlight();
1523       }
1524     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1525       initialMode = TwoMachinesPlay;
1526     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1527       initialMode = AnalyzeFile;
1528     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1529       initialMode = AnalyzeMode;
1530     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1531       initialMode = MachinePlaysWhite;
1532     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1533       initialMode = MachinePlaysBlack;
1534     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1535       initialMode = EditGame;
1536     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1537       initialMode = EditPosition;
1538     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1539       initialMode = Training;
1540     } else {
1541       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1542       if( (len >= MSG_SIZ) && appData.debugMode )
1543         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1544
1545       DisplayFatalError(buf, 0, 2);
1546       return;
1547     }
1548
1549     if (appData.matchMode) {
1550         if(appData.tourneyFile[0]) { // start tourney from command line
1551             FILE *f;
1552             if(f = fopen(appData.tourneyFile, "r")) {
1553                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1554                 fclose(f);
1555                 appData.clockMode = TRUE;
1556                 SetGNUMode();
1557             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1558         }
1559         MatchEvent(TRUE);
1560     } else if (*appData.cmailGameName != NULLCHAR) {
1561         /* Set up cmail mode */
1562         ReloadCmailMsgEvent(TRUE);
1563     } else {
1564         /* Set up other modes */
1565         if (initialMode == AnalyzeFile) {
1566           if (*appData.loadGameFile == NULLCHAR) {
1567             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1568             return;
1569           }
1570         }
1571         if (*appData.loadGameFile != NULLCHAR) {
1572             (void) LoadGameFromFile(appData.loadGameFile,
1573                                     appData.loadGameIndex,
1574                                     appData.loadGameFile, TRUE);
1575         } else if (*appData.loadPositionFile != NULLCHAR) {
1576             (void) LoadPositionFromFile(appData.loadPositionFile,
1577                                         appData.loadPositionIndex,
1578                                         appData.loadPositionFile);
1579             /* [HGM] try to make self-starting even after FEN load */
1580             /* to allow automatic setup of fairy variants with wtm */
1581             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1582                 gameMode = BeginningOfGame;
1583                 setboardSpoiledMachineBlack = 1;
1584             }
1585             /* [HGM] loadPos: make that every new game uses the setup */
1586             /* from file as long as we do not switch variant          */
1587             if(!blackPlaysFirst) {
1588                 startedFromPositionFile = TRUE;
1589                 CopyBoard(filePosition, boards[0]);
1590             }
1591         }
1592         if (initialMode == AnalyzeMode) {
1593           if (appData.noChessProgram) {
1594             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1595             return;
1596           }
1597           if (appData.icsActive) {
1598             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1599             return;
1600           }
1601           AnalyzeModeEvent();
1602         } else if (initialMode == AnalyzeFile) {
1603           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1604           ShowThinkingEvent();
1605           AnalyzeFileEvent();
1606           AnalysisPeriodicEvent(1);
1607         } else if (initialMode == MachinePlaysWhite) {
1608           if (appData.noChessProgram) {
1609             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1610                               0, 2);
1611             return;
1612           }
1613           if (appData.icsActive) {
1614             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1615                               0, 2);
1616             return;
1617           }
1618           MachineWhiteEvent();
1619         } else if (initialMode == MachinePlaysBlack) {
1620           if (appData.noChessProgram) {
1621             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1622                               0, 2);
1623             return;
1624           }
1625           if (appData.icsActive) {
1626             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1627                               0, 2);
1628             return;
1629           }
1630           MachineBlackEvent();
1631         } else if (initialMode == TwoMachinesPlay) {
1632           if (appData.noChessProgram) {
1633             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1634                               0, 2);
1635             return;
1636           }
1637           if (appData.icsActive) {
1638             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1639                               0, 2);
1640             return;
1641           }
1642           TwoMachinesEvent();
1643         } else if (initialMode == EditGame) {
1644           EditGameEvent();
1645         } else if (initialMode == EditPosition) {
1646           EditPositionEvent();
1647         } else if (initialMode == Training) {
1648           if (*appData.loadGameFile == NULLCHAR) {
1649             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1650             return;
1651           }
1652           TrainingEvent();
1653         }
1654     }
1655 }
1656
1657 void
1658 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1659 {
1660     DisplayBook(current+1);
1661
1662     MoveHistorySet( movelist, first, last, current, pvInfoList );
1663
1664     EvalGraphSet( first, last, current, pvInfoList );
1665
1666     MakeEngineOutputTitle();
1667 }
1668
1669 /*
1670  * Establish will establish a contact to a remote host.port.
1671  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1672  *  used to talk to the host.
1673  * Returns 0 if okay, error code if not.
1674  */
1675 int
1676 establish ()
1677 {
1678     char buf[MSG_SIZ];
1679
1680     if (*appData.icsCommPort != NULLCHAR) {
1681         /* Talk to the host through a serial comm port */
1682         return OpenCommPort(appData.icsCommPort, &icsPR);
1683
1684     } else if (*appData.gateway != NULLCHAR) {
1685         if (*appData.remoteShell == NULLCHAR) {
1686             /* Use the rcmd protocol to run telnet program on a gateway host */
1687             snprintf(buf, sizeof(buf), "%s %s %s",
1688                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1689             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1690
1691         } else {
1692             /* Use the rsh program to run telnet program on a gateway host */
1693             if (*appData.remoteUser == NULLCHAR) {
1694                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1695                         appData.gateway, appData.telnetProgram,
1696                         appData.icsHost, appData.icsPort);
1697             } else {
1698                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1699                         appData.remoteShell, appData.gateway,
1700                         appData.remoteUser, appData.telnetProgram,
1701                         appData.icsHost, appData.icsPort);
1702             }
1703             return StartChildProcess(buf, "", &icsPR);
1704
1705         }
1706     } else if (appData.useTelnet) {
1707         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1708
1709     } else {
1710         /* TCP socket interface differs somewhat between
1711            Unix and NT; handle details in the front end.
1712            */
1713         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1714     }
1715 }
1716
1717 void
1718 EscapeExpand (char *p, char *q)
1719 {       // [HGM] initstring: routine to shape up string arguments
1720         while(*p++ = *q++) if(p[-1] == '\\')
1721             switch(*q++) {
1722                 case 'n': p[-1] = '\n'; break;
1723                 case 'r': p[-1] = '\r'; break;
1724                 case 't': p[-1] = '\t'; break;
1725                 case '\\': p[-1] = '\\'; break;
1726                 case 0: *p = 0; return;
1727                 default: p[-1] = q[-1]; break;
1728             }
1729 }
1730
1731 void
1732 show_bytes (FILE *fp, char *buf, int count)
1733 {
1734     while (count--) {
1735         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1736             fprintf(fp, "\\%03o", *buf & 0xff);
1737         } else {
1738             putc(*buf, fp);
1739         }
1740         buf++;
1741     }
1742     fflush(fp);
1743 }
1744
1745 /* Returns an errno value */
1746 int
1747 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1748 {
1749     char buf[8192], *p, *q, *buflim;
1750     int left, newcount, outcount;
1751
1752     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1753         *appData.gateway != NULLCHAR) {
1754         if (appData.debugMode) {
1755             fprintf(debugFP, ">ICS: ");
1756             show_bytes(debugFP, message, count);
1757             fprintf(debugFP, "\n");
1758         }
1759         return OutputToProcess(pr, message, count, outError);
1760     }
1761
1762     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1763     p = message;
1764     q = buf;
1765     left = count;
1766     newcount = 0;
1767     while (left) {
1768         if (q >= buflim) {
1769             if (appData.debugMode) {
1770                 fprintf(debugFP, ">ICS: ");
1771                 show_bytes(debugFP, buf, newcount);
1772                 fprintf(debugFP, "\n");
1773             }
1774             outcount = OutputToProcess(pr, buf, newcount, outError);
1775             if (outcount < newcount) return -1; /* to be sure */
1776             q = buf;
1777             newcount = 0;
1778         }
1779         if (*p == '\n') {
1780             *q++ = '\r';
1781             newcount++;
1782         } else if (((unsigned char) *p) == TN_IAC) {
1783             *q++ = (char) TN_IAC;
1784             newcount ++;
1785         }
1786         *q++ = *p++;
1787         newcount++;
1788         left--;
1789     }
1790     if (appData.debugMode) {
1791         fprintf(debugFP, ">ICS: ");
1792         show_bytes(debugFP, buf, newcount);
1793         fprintf(debugFP, "\n");
1794     }
1795     outcount = OutputToProcess(pr, buf, newcount, outError);
1796     if (outcount < newcount) return -1; /* to be sure */
1797     return count;
1798 }
1799
1800 void
1801 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1802 {
1803     int outError, outCount;
1804     static int gotEof = 0;
1805
1806     /* Pass data read from player on to ICS */
1807     if (count > 0) {
1808         gotEof = 0;
1809         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1810         if (outCount < count) {
1811             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1812         }
1813     } else if (count < 0) {
1814         RemoveInputSource(isr);
1815         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1816     } else if (gotEof++ > 0) {
1817         RemoveInputSource(isr);
1818         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1819     }
1820 }
1821
1822 void
1823 KeepAlive ()
1824 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1825     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1826     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1827     SendToICS("date\n");
1828     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1829 }
1830
1831 /* added routine for printf style output to ics */
1832 void
1833 ics_printf (char *format, ...)
1834 {
1835     char buffer[MSG_SIZ];
1836     va_list args;
1837
1838     va_start(args, format);
1839     vsnprintf(buffer, sizeof(buffer), format, args);
1840     buffer[sizeof(buffer)-1] = '\0';
1841     SendToICS(buffer);
1842     va_end(args);
1843 }
1844
1845 void
1846 SendToICS (char *s)
1847 {
1848     int count, outCount, outError;
1849
1850     if (icsPR == NoProc) return;
1851
1852     count = strlen(s);
1853     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1854     if (outCount < count) {
1855         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1856     }
1857 }
1858
1859 /* This is used for sending logon scripts to the ICS. Sending
1860    without a delay causes problems when using timestamp on ICC
1861    (at least on my machine). */
1862 void
1863 SendToICSDelayed (char *s, long msdelay)
1864 {
1865     int count, outCount, outError;
1866
1867     if (icsPR == NoProc) return;
1868
1869     count = strlen(s);
1870     if (appData.debugMode) {
1871         fprintf(debugFP, ">ICS: ");
1872         show_bytes(debugFP, s, count);
1873         fprintf(debugFP, "\n");
1874     }
1875     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1876                                       msdelay);
1877     if (outCount < count) {
1878         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1879     }
1880 }
1881
1882
1883 /* Remove all highlighting escape sequences in s
1884    Also deletes any suffix starting with '('
1885    */
1886 char *
1887 StripHighlightAndTitle (char *s)
1888 {
1889     static char retbuf[MSG_SIZ];
1890     char *p = retbuf;
1891
1892     while (*s != NULLCHAR) {
1893         while (*s == '\033') {
1894             while (*s != NULLCHAR && !isalpha(*s)) s++;
1895             if (*s != NULLCHAR) s++;
1896         }
1897         while (*s != NULLCHAR && *s != '\033') {
1898             if (*s == '(' || *s == '[') {
1899                 *p = NULLCHAR;
1900                 return retbuf;
1901             }
1902             *p++ = *s++;
1903         }
1904     }
1905     *p = NULLCHAR;
1906     return retbuf;
1907 }
1908
1909 /* Remove all highlighting escape sequences in s */
1910 char *
1911 StripHighlight (char *s)
1912 {
1913     static char retbuf[MSG_SIZ];
1914     char *p = retbuf;
1915
1916     while (*s != NULLCHAR) {
1917         while (*s == '\033') {
1918             while (*s != NULLCHAR && !isalpha(*s)) s++;
1919             if (*s != NULLCHAR) s++;
1920         }
1921         while (*s != NULLCHAR && *s != '\033') {
1922             *p++ = *s++;
1923         }
1924     }
1925     *p = NULLCHAR;
1926     return retbuf;
1927 }
1928
1929 char *variantNames[] = VARIANT_NAMES;
1930 char *
1931 VariantName (VariantClass v)
1932 {
1933     return variantNames[v];
1934 }
1935
1936
1937 /* Identify a variant from the strings the chess servers use or the
1938    PGN Variant tag names we use. */
1939 VariantClass
1940 StringToVariant (char *e)
1941 {
1942     char *p;
1943     int wnum = -1;
1944     VariantClass v = VariantNormal;
1945     int i, found = FALSE;
1946     char buf[MSG_SIZ];
1947     int len;
1948
1949     if (!e) return v;
1950
1951     /* [HGM] skip over optional board-size prefixes */
1952     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1953         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1954         while( *e++ != '_');
1955     }
1956
1957     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1958         v = VariantNormal;
1959         found = TRUE;
1960     } else
1961     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1962       if (StrCaseStr(e, variantNames[i])) {
1963         v = (VariantClass) i;
1964         found = TRUE;
1965         break;
1966       }
1967     }
1968
1969     if (!found) {
1970       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1971           || StrCaseStr(e, "wild/fr")
1972           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1973         v = VariantFischeRandom;
1974       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1975                  (i = 1, p = StrCaseStr(e, "w"))) {
1976         p += i;
1977         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1978         if (isdigit(*p)) {
1979           wnum = atoi(p);
1980         } else {
1981           wnum = -1;
1982         }
1983         switch (wnum) {
1984         case 0: /* FICS only, actually */
1985         case 1:
1986           /* Castling legal even if K starts on d-file */
1987           v = VariantWildCastle;
1988           break;
1989         case 2:
1990         case 3:
1991         case 4:
1992           /* Castling illegal even if K & R happen to start in
1993              normal positions. */
1994           v = VariantNoCastle;
1995           break;
1996         case 5:
1997         case 7:
1998         case 8:
1999         case 10:
2000         case 11:
2001         case 12:
2002         case 13:
2003         case 14:
2004         case 15:
2005         case 18:
2006         case 19:
2007           /* Castling legal iff K & R start in normal positions */
2008           v = VariantNormal;
2009           break;
2010         case 6:
2011         case 20:
2012         case 21:
2013           /* Special wilds for position setup; unclear what to do here */
2014           v = VariantLoadable;
2015           break;
2016         case 9:
2017           /* Bizarre ICC game */
2018           v = VariantTwoKings;
2019           break;
2020         case 16:
2021           v = VariantKriegspiel;
2022           break;
2023         case 17:
2024           v = VariantLosers;
2025           break;
2026         case 22:
2027           v = VariantFischeRandom;
2028           break;
2029         case 23:
2030           v = VariantCrazyhouse;
2031           break;
2032         case 24:
2033           v = VariantBughouse;
2034           break;
2035         case 25:
2036           v = Variant3Check;
2037           break;
2038         case 26:
2039           /* Not quite the same as FICS suicide! */
2040           v = VariantGiveaway;
2041           break;
2042         case 27:
2043           v = VariantAtomic;
2044           break;
2045         case 28:
2046           v = VariantShatranj;
2047           break;
2048
2049         /* Temporary names for future ICC types.  The name *will* change in
2050            the next xboard/WinBoard release after ICC defines it. */
2051         case 29:
2052           v = Variant29;
2053           break;
2054         case 30:
2055           v = Variant30;
2056           break;
2057         case 31:
2058           v = Variant31;
2059           break;
2060         case 32:
2061           v = Variant32;
2062           break;
2063         case 33:
2064           v = Variant33;
2065           break;
2066         case 34:
2067           v = Variant34;
2068           break;
2069         case 35:
2070           v = Variant35;
2071           break;
2072         case 36:
2073           v = Variant36;
2074           break;
2075         case 37:
2076           v = VariantShogi;
2077           break;
2078         case 38:
2079           v = VariantXiangqi;
2080           break;
2081         case 39:
2082           v = VariantCourier;
2083           break;
2084         case 40:
2085           v = VariantGothic;
2086           break;
2087         case 41:
2088           v = VariantCapablanca;
2089           break;
2090         case 42:
2091           v = VariantKnightmate;
2092           break;
2093         case 43:
2094           v = VariantFairy;
2095           break;
2096         case 44:
2097           v = VariantCylinder;
2098           break;
2099         case 45:
2100           v = VariantFalcon;
2101           break;
2102         case 46:
2103           v = VariantCapaRandom;
2104           break;
2105         case 47:
2106           v = VariantBerolina;
2107           break;
2108         case 48:
2109           v = VariantJanus;
2110           break;
2111         case 49:
2112           v = VariantSuper;
2113           break;
2114         case 50:
2115           v = VariantGreat;
2116           break;
2117         case -1:
2118           /* Found "wild" or "w" in the string but no number;
2119              must assume it's normal chess. */
2120           v = VariantNormal;
2121           break;
2122         default:
2123           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2124           if( (len >= MSG_SIZ) && appData.debugMode )
2125             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2126
2127           DisplayError(buf, 0);
2128           v = VariantUnknown;
2129           break;
2130         }
2131       }
2132     }
2133     if (appData.debugMode) {
2134       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2135               e, wnum, VariantName(v));
2136     }
2137     return v;
2138 }
2139
2140 static int leftover_start = 0, leftover_len = 0;
2141 char star_match[STAR_MATCH_N][MSG_SIZ];
2142
2143 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2144    advance *index beyond it, and set leftover_start to the new value of
2145    *index; else return FALSE.  If pattern contains the character '*', it
2146    matches any sequence of characters not containing '\r', '\n', or the
2147    character following the '*' (if any), and the matched sequence(s) are
2148    copied into star_match.
2149    */
2150 int
2151 looking_at ( char *buf, int *index, char *pattern)
2152 {
2153     char *bufp = &buf[*index], *patternp = pattern;
2154     int star_count = 0;
2155     char *matchp = star_match[0];
2156
2157     for (;;) {
2158         if (*patternp == NULLCHAR) {
2159             *index = leftover_start = bufp - buf;
2160             *matchp = NULLCHAR;
2161             return TRUE;
2162         }
2163         if (*bufp == NULLCHAR) return FALSE;
2164         if (*patternp == '*') {
2165             if (*bufp == *(patternp + 1)) {
2166                 *matchp = NULLCHAR;
2167                 matchp = star_match[++star_count];
2168                 patternp += 2;
2169                 bufp++;
2170                 continue;
2171             } else if (*bufp == '\n' || *bufp == '\r') {
2172                 patternp++;
2173                 if (*patternp == NULLCHAR)
2174                   continue;
2175                 else
2176                   return FALSE;
2177             } else {
2178                 *matchp++ = *bufp++;
2179                 continue;
2180             }
2181         }
2182         if (*patternp != *bufp) return FALSE;
2183         patternp++;
2184         bufp++;
2185     }
2186 }
2187
2188 void
2189 SendToPlayer (char *data, int length)
2190 {
2191     int error, outCount;
2192     outCount = OutputToProcess(NoProc, data, length, &error);
2193     if (outCount < length) {
2194         DisplayFatalError(_("Error writing to display"), error, 1);
2195     }
2196 }
2197
2198 void
2199 PackHolding (char packed[], char *holding)
2200 {
2201     char *p = holding;
2202     char *q = packed;
2203     int runlength = 0;
2204     int curr = 9999;
2205     do {
2206         if (*p == curr) {
2207             runlength++;
2208         } else {
2209             switch (runlength) {
2210               case 0:
2211                 break;
2212               case 1:
2213                 *q++ = curr;
2214                 break;
2215               case 2:
2216                 *q++ = curr;
2217                 *q++ = curr;
2218                 break;
2219               default:
2220                 sprintf(q, "%d", runlength);
2221                 while (*q) q++;
2222                 *q++ = curr;
2223                 break;
2224             }
2225             runlength = 1;
2226             curr = *p;
2227         }
2228     } while (*p++);
2229     *q = NULLCHAR;
2230 }
2231
2232 /* Telnet protocol requests from the front end */
2233 void
2234 TelnetRequest (unsigned char ddww, unsigned char option)
2235 {
2236     unsigned char msg[3];
2237     int outCount, outError;
2238
2239     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2240
2241     if (appData.debugMode) {
2242         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2243         switch (ddww) {
2244           case TN_DO:
2245             ddwwStr = "DO";
2246             break;
2247           case TN_DONT:
2248             ddwwStr = "DONT";
2249             break;
2250           case TN_WILL:
2251             ddwwStr = "WILL";
2252             break;
2253           case TN_WONT:
2254             ddwwStr = "WONT";
2255             break;
2256           default:
2257             ddwwStr = buf1;
2258             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2259             break;
2260         }
2261         switch (option) {
2262           case TN_ECHO:
2263             optionStr = "ECHO";
2264             break;
2265           default:
2266             optionStr = buf2;
2267             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2268             break;
2269         }
2270         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2271     }
2272     msg[0] = TN_IAC;
2273     msg[1] = ddww;
2274     msg[2] = option;
2275     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2276     if (outCount < 3) {
2277         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2278     }
2279 }
2280
2281 void
2282 DoEcho ()
2283 {
2284     if (!appData.icsActive) return;
2285     TelnetRequest(TN_DO, TN_ECHO);
2286 }
2287
2288 void
2289 DontEcho ()
2290 {
2291     if (!appData.icsActive) return;
2292     TelnetRequest(TN_DONT, TN_ECHO);
2293 }
2294
2295 void
2296 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2297 {
2298     /* put the holdings sent to us by the server on the board holdings area */
2299     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2300     char p;
2301     ChessSquare piece;
2302
2303     if(gameInfo.holdingsWidth < 2)  return;
2304     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2305         return; // prevent overwriting by pre-board holdings
2306
2307     if( (int)lowestPiece >= BlackPawn ) {
2308         holdingsColumn = 0;
2309         countsColumn = 1;
2310         holdingsStartRow = BOARD_HEIGHT-1;
2311         direction = -1;
2312     } else {
2313         holdingsColumn = BOARD_WIDTH-1;
2314         countsColumn = BOARD_WIDTH-2;
2315         holdingsStartRow = 0;
2316         direction = 1;
2317     }
2318
2319     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2320         board[i][holdingsColumn] = EmptySquare;
2321         board[i][countsColumn]   = (ChessSquare) 0;
2322     }
2323     while( (p=*holdings++) != NULLCHAR ) {
2324         piece = CharToPiece( ToUpper(p) );
2325         if(piece == EmptySquare) continue;
2326         /*j = (int) piece - (int) WhitePawn;*/
2327         j = PieceToNumber(piece);
2328         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2329         if(j < 0) continue;               /* should not happen */
2330         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2331         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2332         board[holdingsStartRow+j*direction][countsColumn]++;
2333     }
2334 }
2335
2336
2337 void
2338 VariantSwitch (Board board, VariantClass newVariant)
2339 {
2340    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2341    static Board oldBoard;
2342
2343    startedFromPositionFile = FALSE;
2344    if(gameInfo.variant == newVariant) return;
2345
2346    /* [HGM] This routine is called each time an assignment is made to
2347     * gameInfo.variant during a game, to make sure the board sizes
2348     * are set to match the new variant. If that means adding or deleting
2349     * holdings, we shift the playing board accordingly
2350     * This kludge is needed because in ICS observe mode, we get boards
2351     * of an ongoing game without knowing the variant, and learn about the
2352     * latter only later. This can be because of the move list we requested,
2353     * in which case the game history is refilled from the beginning anyway,
2354     * but also when receiving holdings of a crazyhouse game. In the latter
2355     * case we want to add those holdings to the already received position.
2356     */
2357
2358
2359    if (appData.debugMode) {
2360      fprintf(debugFP, "Switch board from %s to %s\n",
2361              VariantName(gameInfo.variant), VariantName(newVariant));
2362      setbuf(debugFP, NULL);
2363    }
2364    shuffleOpenings = 0;       /* [HGM] shuffle */
2365    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2366    switch(newVariant)
2367      {
2368      case VariantShogi:
2369        newWidth = 9;  newHeight = 9;
2370        gameInfo.holdingsSize = 7;
2371      case VariantBughouse:
2372      case VariantCrazyhouse:
2373        newHoldingsWidth = 2; break;
2374      case VariantGreat:
2375        newWidth = 10;
2376      case VariantSuper:
2377        newHoldingsWidth = 2;
2378        gameInfo.holdingsSize = 8;
2379        break;
2380      case VariantGothic:
2381      case VariantCapablanca:
2382      case VariantCapaRandom:
2383        newWidth = 10;
2384      default:
2385        newHoldingsWidth = gameInfo.holdingsSize = 0;
2386      };
2387
2388    if(newWidth  != gameInfo.boardWidth  ||
2389       newHeight != gameInfo.boardHeight ||
2390       newHoldingsWidth != gameInfo.holdingsWidth ) {
2391
2392      /* shift position to new playing area, if needed */
2393      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2394        for(i=0; i<BOARD_HEIGHT; i++)
2395          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2396            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2397              board[i][j];
2398        for(i=0; i<newHeight; i++) {
2399          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2400          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2401        }
2402      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2403        for(i=0; i<BOARD_HEIGHT; i++)
2404          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2405            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2406              board[i][j];
2407      }
2408      gameInfo.boardWidth  = newWidth;
2409      gameInfo.boardHeight = newHeight;
2410      gameInfo.holdingsWidth = newHoldingsWidth;
2411      gameInfo.variant = newVariant;
2412      InitDrawingSizes(-2, 0);
2413    } else gameInfo.variant = newVariant;
2414    CopyBoard(oldBoard, board);   // remember correctly formatted board
2415      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2416    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2417 }
2418
2419 static int loggedOn = FALSE;
2420
2421 /*-- Game start info cache: --*/
2422 int gs_gamenum;
2423 char gs_kind[MSG_SIZ];
2424 static char player1Name[128] = "";
2425 static char player2Name[128] = "";
2426 static char cont_seq[] = "\n\\   ";
2427 static int player1Rating = -1;
2428 static int player2Rating = -1;
2429 /*----------------------------*/
2430
2431 ColorClass curColor = ColorNormal;
2432 int suppressKibitz = 0;
2433
2434 // [HGM] seekgraph
2435 Boolean soughtPending = FALSE;
2436 Boolean seekGraphUp;
2437 #define MAX_SEEK_ADS 200
2438 #define SQUARE 0x80
2439 char *seekAdList[MAX_SEEK_ADS];
2440 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2441 float tcList[MAX_SEEK_ADS];
2442 char colorList[MAX_SEEK_ADS];
2443 int nrOfSeekAds = 0;
2444 int minRating = 1010, maxRating = 2800;
2445 int hMargin = 10, vMargin = 20, h, w;
2446 extern int squareSize, lineGap;
2447
2448 void
2449 PlotSeekAd (int i)
2450 {
2451         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2452         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2453         if(r < minRating+100 && r >=0 ) r = minRating+100;
2454         if(r > maxRating) r = maxRating;
2455         if(tc < 1.) tc = 1.;
2456         if(tc > 95.) tc = 95.;
2457         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2458         y = ((double)r - minRating)/(maxRating - minRating)
2459             * (h-vMargin-squareSize/8-1) + vMargin;
2460         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2461         if(strstr(seekAdList[i], " u ")) color = 1;
2462         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2463            !strstr(seekAdList[i], "bullet") &&
2464            !strstr(seekAdList[i], "blitz") &&
2465            !strstr(seekAdList[i], "standard") ) color = 2;
2466         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2467         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2468 }
2469
2470 void
2471 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2472 {
2473         char buf[MSG_SIZ], *ext = "";
2474         VariantClass v = StringToVariant(type);
2475         if(strstr(type, "wild")) {
2476             ext = type + 4; // append wild number
2477             if(v == VariantFischeRandom) type = "chess960"; else
2478             if(v == VariantLoadable) type = "setup"; else
2479             type = VariantName(v);
2480         }
2481         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2482         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2483             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2484             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2485             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2486             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2487             seekNrList[nrOfSeekAds] = nr;
2488             zList[nrOfSeekAds] = 0;
2489             seekAdList[nrOfSeekAds++] = StrSave(buf);
2490             if(plot) PlotSeekAd(nrOfSeekAds-1);
2491         }
2492 }
2493
2494 void
2495 EraseSeekDot (int i)
2496 {
2497     int x = xList[i], y = yList[i], d=squareSize/4, k;
2498     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2499     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2500     // now replot every dot that overlapped
2501     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2502         int xx = xList[k], yy = yList[k];
2503         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2504             DrawSeekDot(xx, yy, colorList[k]);
2505     }
2506 }
2507
2508 void
2509 RemoveSeekAd (int nr)
2510 {
2511         int i;
2512         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2513             EraseSeekDot(i);
2514             if(seekAdList[i]) free(seekAdList[i]);
2515             seekAdList[i] = seekAdList[--nrOfSeekAds];
2516             seekNrList[i] = seekNrList[nrOfSeekAds];
2517             ratingList[i] = ratingList[nrOfSeekAds];
2518             colorList[i]  = colorList[nrOfSeekAds];
2519             tcList[i] = tcList[nrOfSeekAds];
2520             xList[i]  = xList[nrOfSeekAds];
2521             yList[i]  = yList[nrOfSeekAds];
2522             zList[i]  = zList[nrOfSeekAds];
2523             seekAdList[nrOfSeekAds] = NULL;
2524             break;
2525         }
2526 }
2527
2528 Boolean
2529 MatchSoughtLine (char *line)
2530 {
2531     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2532     int nr, base, inc, u=0; char dummy;
2533
2534     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2535        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2536        (u=1) &&
2537        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2538         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2539         // match: compact and save the line
2540         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2541         return TRUE;
2542     }
2543     return FALSE;
2544 }
2545
2546 int
2547 DrawSeekGraph ()
2548 {
2549     int i;
2550     if(!seekGraphUp) return FALSE;
2551     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2552     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2553
2554     DrawSeekBackground(0, 0, w, h);
2555     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2556     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2557     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2558         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2559         yy = h-1-yy;
2560         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2561         if(i%500 == 0) {
2562             char buf[MSG_SIZ];
2563             snprintf(buf, MSG_SIZ, "%d", i);
2564             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2565         }
2566     }
2567     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2568     for(i=1; i<100; i+=(i<10?1:5)) {
2569         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2570         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2571         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2572             char buf[MSG_SIZ];
2573             snprintf(buf, MSG_SIZ, "%d", i);
2574             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2575         }
2576     }
2577     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2578     return TRUE;
2579 }
2580
2581 int
2582 SeekGraphClick (ClickType click, int x, int y, int moving)
2583 {
2584     static int lastDown = 0, displayed = 0, lastSecond;
2585     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2586         if(click == Release || moving) return FALSE;
2587         nrOfSeekAds = 0;
2588         soughtPending = TRUE;
2589         SendToICS(ics_prefix);
2590         SendToICS("sought\n"); // should this be "sought all"?
2591     } else { // issue challenge based on clicked ad
2592         int dist = 10000; int i, closest = 0, second = 0;
2593         for(i=0; i<nrOfSeekAds; i++) {
2594             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2595             if(d < dist) { dist = d; closest = i; }
2596             second += (d - zList[i] < 120); // count in-range ads
2597             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2598         }
2599         if(dist < 120) {
2600             char buf[MSG_SIZ];
2601             second = (second > 1);
2602             if(displayed != closest || second != lastSecond) {
2603                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2604                 lastSecond = second; displayed = closest;
2605             }
2606             if(click == Press) {
2607                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2608                 lastDown = closest;
2609                 return TRUE;
2610             } // on press 'hit', only show info
2611             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2612             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2613             SendToICS(ics_prefix);
2614             SendToICS(buf);
2615             return TRUE; // let incoming board of started game pop down the graph
2616         } else if(click == Release) { // release 'miss' is ignored
2617             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2618             if(moving == 2) { // right up-click
2619                 nrOfSeekAds = 0; // refresh graph
2620                 soughtPending = TRUE;
2621                 SendToICS(ics_prefix);
2622                 SendToICS("sought\n"); // should this be "sought all"?
2623             }
2624             return TRUE;
2625         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2626         // press miss or release hit 'pop down' seek graph
2627         seekGraphUp = FALSE;
2628         DrawPosition(TRUE, NULL);
2629     }
2630     return TRUE;
2631 }
2632
2633 void
2634 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2635 {
2636 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2637 #define STARTED_NONE 0
2638 #define STARTED_MOVES 1
2639 #define STARTED_BOARD 2
2640 #define STARTED_OBSERVE 3
2641 #define STARTED_HOLDINGS 4
2642 #define STARTED_CHATTER 5
2643 #define STARTED_COMMENT 6
2644 #define STARTED_MOVES_NOHIDE 7
2645
2646     static int started = STARTED_NONE;
2647     static char parse[20000];
2648     static int parse_pos = 0;
2649     static char buf[BUF_SIZE + 1];
2650     static int firstTime = TRUE, intfSet = FALSE;
2651     static ColorClass prevColor = ColorNormal;
2652     static int savingComment = FALSE;
2653     static int cmatch = 0; // continuation sequence match
2654     char *bp;
2655     char str[MSG_SIZ];
2656     int i, oldi;
2657     int buf_len;
2658     int next_out;
2659     int tkind;
2660     int backup;    /* [DM] For zippy color lines */
2661     char *p;
2662     char talker[MSG_SIZ]; // [HGM] chat
2663     int channel;
2664
2665     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2666
2667     if (appData.debugMode) {
2668       if (!error) {
2669         fprintf(debugFP, "<ICS: ");
2670         show_bytes(debugFP, data, count);
2671         fprintf(debugFP, "\n");
2672       }
2673     }
2674
2675     if (appData.debugMode) { int f = forwardMostMove;
2676         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2677                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2678                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2679     }
2680     if (count > 0) {
2681         /* If last read ended with a partial line that we couldn't parse,
2682            prepend it to the new read and try again. */
2683         if (leftover_len > 0) {
2684             for (i=0; i<leftover_len; i++)
2685               buf[i] = buf[leftover_start + i];
2686         }
2687
2688     /* copy new characters into the buffer */
2689     bp = buf + leftover_len;
2690     buf_len=leftover_len;
2691     for (i=0; i<count; i++)
2692     {
2693         // ignore these
2694         if (data[i] == '\r')
2695             continue;
2696
2697         // join lines split by ICS?
2698         if (!appData.noJoin)
2699         {
2700             /*
2701                 Joining just consists of finding matches against the
2702                 continuation sequence, and discarding that sequence
2703                 if found instead of copying it.  So, until a match
2704                 fails, there's nothing to do since it might be the
2705                 complete sequence, and thus, something we don't want
2706                 copied.
2707             */
2708             if (data[i] == cont_seq[cmatch])
2709             {
2710                 cmatch++;
2711                 if (cmatch == strlen(cont_seq))
2712                 {
2713                     cmatch = 0; // complete match.  just reset the counter
2714
2715                     /*
2716                         it's possible for the ICS to not include the space
2717                         at the end of the last word, making our [correct]
2718                         join operation fuse two separate words.  the server
2719                         does this when the space occurs at the width setting.
2720                     */
2721                     if (!buf_len || buf[buf_len-1] != ' ')
2722                     {
2723                         *bp++ = ' ';
2724                         buf_len++;
2725                     }
2726                 }
2727                 continue;
2728             }
2729             else if (cmatch)
2730             {
2731                 /*
2732                     match failed, so we have to copy what matched before
2733                     falling through and copying this character.  In reality,
2734                     this will only ever be just the newline character, but
2735                     it doesn't hurt to be precise.
2736                 */
2737                 strncpy(bp, cont_seq, cmatch);
2738                 bp += cmatch;
2739                 buf_len += cmatch;
2740                 cmatch = 0;
2741             }
2742         }
2743
2744         // copy this char
2745         *bp++ = data[i];
2746         buf_len++;
2747     }
2748
2749         buf[buf_len] = NULLCHAR;
2750 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2751         next_out = 0;
2752         leftover_start = 0;
2753
2754         i = 0;
2755         while (i < buf_len) {
2756             /* Deal with part of the TELNET option negotiation
2757                protocol.  We refuse to do anything beyond the
2758                defaults, except that we allow the WILL ECHO option,
2759                which ICS uses to turn off password echoing when we are
2760                directly connected to it.  We reject this option
2761                if localLineEditing mode is on (always on in xboard)
2762                and we are talking to port 23, which might be a real
2763                telnet server that will try to keep WILL ECHO on permanently.
2764              */
2765             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2766                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2767                 unsigned char option;
2768                 oldi = i;
2769                 switch ((unsigned char) buf[++i]) {
2770                   case TN_WILL:
2771                     if (appData.debugMode)
2772                       fprintf(debugFP, "\n<WILL ");
2773                     switch (option = (unsigned char) buf[++i]) {
2774                       case TN_ECHO:
2775                         if (appData.debugMode)
2776                           fprintf(debugFP, "ECHO ");
2777                         /* Reply only if this is a change, according
2778                            to the protocol rules. */
2779                         if (remoteEchoOption) break;
2780                         if (appData.localLineEditing &&
2781                             atoi(appData.icsPort) == TN_PORT) {
2782                             TelnetRequest(TN_DONT, TN_ECHO);
2783                         } else {
2784                             EchoOff();
2785                             TelnetRequest(TN_DO, TN_ECHO);
2786                             remoteEchoOption = TRUE;
2787                         }
2788                         break;
2789                       default:
2790                         if (appData.debugMode)
2791                           fprintf(debugFP, "%d ", option);
2792                         /* Whatever this is, we don't want it. */
2793                         TelnetRequest(TN_DONT, option);
2794                         break;
2795                     }
2796                     break;
2797                   case TN_WONT:
2798                     if (appData.debugMode)
2799                       fprintf(debugFP, "\n<WONT ");
2800                     switch (option = (unsigned char) buf[++i]) {
2801                       case TN_ECHO:
2802                         if (appData.debugMode)
2803                           fprintf(debugFP, "ECHO ");
2804                         /* Reply only if this is a change, according
2805                            to the protocol rules. */
2806                         if (!remoteEchoOption) break;
2807                         EchoOn();
2808                         TelnetRequest(TN_DONT, TN_ECHO);
2809                         remoteEchoOption = FALSE;
2810                         break;
2811                       default:
2812                         if (appData.debugMode)
2813                           fprintf(debugFP, "%d ", (unsigned char) option);
2814                         /* Whatever this is, it must already be turned
2815                            off, because we never agree to turn on
2816                            anything non-default, so according to the
2817                            protocol rules, we don't reply. */
2818                         break;
2819                     }
2820                     break;
2821                   case TN_DO:
2822                     if (appData.debugMode)
2823                       fprintf(debugFP, "\n<DO ");
2824                     switch (option = (unsigned char) buf[++i]) {
2825                       default:
2826                         /* Whatever this is, we refuse to do it. */
2827                         if (appData.debugMode)
2828                           fprintf(debugFP, "%d ", option);
2829                         TelnetRequest(TN_WONT, option);
2830                         break;
2831                     }
2832                     break;
2833                   case TN_DONT:
2834                     if (appData.debugMode)
2835                       fprintf(debugFP, "\n<DONT ");
2836                     switch (option = (unsigned char) buf[++i]) {
2837                       default:
2838                         if (appData.debugMode)
2839                           fprintf(debugFP, "%d ", option);
2840                         /* Whatever this is, we are already not doing
2841                            it, because we never agree to do anything
2842                            non-default, so according to the protocol
2843                            rules, we don't reply. */
2844                         break;
2845                     }
2846                     break;
2847                   case TN_IAC:
2848                     if (appData.debugMode)
2849                       fprintf(debugFP, "\n<IAC ");
2850                     /* Doubled IAC; pass it through */
2851                     i--;
2852                     break;
2853                   default:
2854                     if (appData.debugMode)
2855                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2856                     /* Drop all other telnet commands on the floor */
2857                     break;
2858                 }
2859                 if (oldi > next_out)
2860                   SendToPlayer(&buf[next_out], oldi - next_out);
2861                 if (++i > next_out)
2862                   next_out = i;
2863                 continue;
2864             }
2865
2866             /* OK, this at least will *usually* work */
2867             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2868                 loggedOn = TRUE;
2869             }
2870
2871             if (loggedOn && !intfSet) {
2872                 if (ics_type == ICS_ICC) {
2873                   snprintf(str, MSG_SIZ,
2874                           "/set-quietly interface %s\n/set-quietly style 12\n",
2875                           programVersion);
2876                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2877                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2878                 } else if (ics_type == ICS_CHESSNET) {
2879                   snprintf(str, MSG_SIZ, "/style 12\n");
2880                 } else {
2881                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2882                   strcat(str, programVersion);
2883                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2884                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2885                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2886 #ifdef WIN32
2887                   strcat(str, "$iset nohighlight 1\n");
2888 #endif
2889                   strcat(str, "$iset lock 1\n$style 12\n");
2890                 }
2891                 SendToICS(str);
2892                 NotifyFrontendLogin();
2893                 intfSet = TRUE;
2894             }
2895
2896             if (started == STARTED_COMMENT) {
2897                 /* Accumulate characters in comment */
2898                 parse[parse_pos++] = buf[i];
2899                 if (buf[i] == '\n') {
2900                     parse[parse_pos] = NULLCHAR;
2901                     if(chattingPartner>=0) {
2902                         char mess[MSG_SIZ];
2903                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2904                         OutputChatMessage(chattingPartner, mess);
2905                         chattingPartner = -1;
2906                         next_out = i+1; // [HGM] suppress printing in ICS window
2907                     } else
2908                     if(!suppressKibitz) // [HGM] kibitz
2909                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2910                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2911                         int nrDigit = 0, nrAlph = 0, j;
2912                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2913                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2914                         parse[parse_pos] = NULLCHAR;
2915                         // try to be smart: if it does not look like search info, it should go to
2916                         // ICS interaction window after all, not to engine-output window.
2917                         for(j=0; j<parse_pos; j++) { // count letters and digits
2918                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2919                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2920                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2921                         }
2922                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2923                             int depth=0; float score;
2924                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2925                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2926                                 pvInfoList[forwardMostMove-1].depth = depth;
2927                                 pvInfoList[forwardMostMove-1].score = 100*score;
2928                             }
2929                             OutputKibitz(suppressKibitz, parse);
2930                         } else {
2931                             char tmp[MSG_SIZ];
2932                             if(gameMode == IcsObserving) // restore original ICS messages
2933                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2934                             else
2935                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2936                             SendToPlayer(tmp, strlen(tmp));
2937                         }
2938                         next_out = i+1; // [HGM] suppress printing in ICS window
2939                     }
2940                     started = STARTED_NONE;
2941                 } else {
2942                     /* Don't match patterns against characters in comment */
2943                     i++;
2944                     continue;
2945                 }
2946             }
2947             if (started == STARTED_CHATTER) {
2948                 if (buf[i] != '\n') {
2949                     /* Don't match patterns against characters in chatter */
2950                     i++;
2951                     continue;
2952                 }
2953                 started = STARTED_NONE;
2954                 if(suppressKibitz) next_out = i+1;
2955             }
2956
2957             /* Kludge to deal with rcmd protocol */
2958             if (firstTime && looking_at(buf, &i, "\001*")) {
2959                 DisplayFatalError(&buf[1], 0, 1);
2960                 continue;
2961             } else {
2962                 firstTime = FALSE;
2963             }
2964
2965             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2966                 ics_type = ICS_ICC;
2967                 ics_prefix = "/";
2968                 if (appData.debugMode)
2969                   fprintf(debugFP, "ics_type %d\n", ics_type);
2970                 continue;
2971             }
2972             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2973                 ics_type = ICS_FICS;
2974                 ics_prefix = "$";
2975                 if (appData.debugMode)
2976                   fprintf(debugFP, "ics_type %d\n", ics_type);
2977                 continue;
2978             }
2979             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2980                 ics_type = ICS_CHESSNET;
2981                 ics_prefix = "/";
2982                 if (appData.debugMode)
2983                   fprintf(debugFP, "ics_type %d\n", ics_type);
2984                 continue;
2985             }
2986
2987             if (!loggedOn &&
2988                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2989                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2990                  looking_at(buf, &i, "will be \"*\""))) {
2991               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2992               continue;
2993             }
2994
2995             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2996               char buf[MSG_SIZ];
2997               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2998               DisplayIcsInteractionTitle(buf);
2999               have_set_title = TRUE;
3000             }
3001
3002             /* skip finger notes */
3003             if (started == STARTED_NONE &&
3004                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3005                  (buf[i] == '1' && buf[i+1] == '0')) &&
3006                 buf[i+2] == ':' && buf[i+3] == ' ') {
3007               started = STARTED_CHATTER;
3008               i += 3;
3009               continue;
3010             }
3011
3012             oldi = i;
3013             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3014             if(appData.seekGraph) {
3015                 if(soughtPending && MatchSoughtLine(buf+i)) {
3016                     i = strstr(buf+i, "rated") - buf;
3017                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3018                     next_out = leftover_start = i;
3019                     started = STARTED_CHATTER;
3020                     suppressKibitz = TRUE;
3021                     continue;
3022                 }
3023                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3024                         && looking_at(buf, &i, "* ads displayed")) {
3025                     soughtPending = FALSE;
3026                     seekGraphUp = TRUE;
3027                     DrawSeekGraph();
3028                     continue;
3029                 }
3030                 if(appData.autoRefresh) {
3031                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3032                         int s = (ics_type == ICS_ICC); // ICC format differs
3033                         if(seekGraphUp)
3034                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3035                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3036                         looking_at(buf, &i, "*% "); // eat prompt
3037                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3038                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3039                         next_out = i; // suppress
3040                         continue;
3041                     }
3042                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3043                         char *p = star_match[0];
3044                         while(*p) {
3045                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3046                             while(*p && *p++ != ' '); // next
3047                         }
3048                         looking_at(buf, &i, "*% "); // eat prompt
3049                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3050                         next_out = i;
3051                         continue;
3052                     }
3053                 }
3054             }
3055
3056             /* skip formula vars */
3057             if (started == STARTED_NONE &&
3058                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3059               started = STARTED_CHATTER;
3060               i += 3;
3061               continue;
3062             }
3063
3064             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3065             if (appData.autoKibitz && started == STARTED_NONE &&
3066                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3067                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3068                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3069                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3070                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3071                         suppressKibitz = TRUE;
3072                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3073                         next_out = i;
3074                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3075                                 && (gameMode == IcsPlayingWhite)) ||
3076                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3077                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3078                             started = STARTED_CHATTER; // own kibitz we simply discard
3079                         else {
3080                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3081                             parse_pos = 0; parse[0] = NULLCHAR;
3082                             savingComment = TRUE;
3083                             suppressKibitz = gameMode != IcsObserving ? 2 :
3084                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3085                         }
3086                         continue;
3087                 } else
3088                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3089                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3090                          && atoi(star_match[0])) {
3091                     // suppress the acknowledgements of our own autoKibitz
3092                     char *p;
3093                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3094                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3095                     SendToPlayer(star_match[0], strlen(star_match[0]));
3096                     if(looking_at(buf, &i, "*% ")) // eat prompt
3097                         suppressKibitz = FALSE;
3098                     next_out = i;
3099                     continue;
3100                 }
3101             } // [HGM] kibitz: end of patch
3102
3103             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3104
3105             // [HGM] chat: intercept tells by users for which we have an open chat window
3106             channel = -1;
3107             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3108                                            looking_at(buf, &i, "* whispers:") ||
3109                                            looking_at(buf, &i, "* kibitzes:") ||
3110                                            looking_at(buf, &i, "* shouts:") ||
3111                                            looking_at(buf, &i, "* c-shouts:") ||
3112                                            looking_at(buf, &i, "--> * ") ||
3113                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3114                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3115                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3116                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3117                 int p;
3118                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3119                 chattingPartner = -1;
3120
3121                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3122                 for(p=0; p<MAX_CHAT; p++) {
3123                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3124                     talker[0] = '['; strcat(talker, "] ");
3125                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3126                     chattingPartner = p; break;
3127                     }
3128                 } else
3129                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3130                 for(p=0; p<MAX_CHAT; p++) {
3131                     if(!strcmp("kibitzes", chatPartner[p])) {
3132                         talker[0] = '['; strcat(talker, "] ");
3133                         chattingPartner = p; break;
3134                     }
3135                 } else
3136                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3137                 for(p=0; p<MAX_CHAT; p++) {
3138                     if(!strcmp("whispers", chatPartner[p])) {
3139                         talker[0] = '['; strcat(talker, "] ");
3140                         chattingPartner = p; break;
3141                     }
3142                 } else
3143                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3144                   if(buf[i-8] == '-' && buf[i-3] == 't')
3145                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3146                     if(!strcmp("c-shouts", chatPartner[p])) {
3147                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3148                         chattingPartner = p; break;
3149                     }
3150                   }
3151                   if(chattingPartner < 0)
3152                   for(p=0; p<MAX_CHAT; p++) {
3153                     if(!strcmp("shouts", chatPartner[p])) {
3154                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3155                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3156                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3157                         chattingPartner = p; break;
3158                     }
3159                   }
3160                 }
3161                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3162                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3163                     talker[0] = 0; Colorize(ColorTell, FALSE);
3164                     chattingPartner = p; break;
3165                 }
3166                 if(chattingPartner<0) i = oldi; else {
3167                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3168                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3169                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3170                     started = STARTED_COMMENT;
3171                     parse_pos = 0; parse[0] = NULLCHAR;
3172                     savingComment = 3 + chattingPartner; // counts as TRUE
3173                     suppressKibitz = TRUE;
3174                     continue;
3175                 }
3176             } // [HGM] chat: end of patch
3177
3178           backup = i;
3179             if (appData.zippyTalk || appData.zippyPlay) {
3180                 /* [DM] Backup address for color zippy lines */
3181 #if ZIPPY
3182                if (loggedOn == TRUE)
3183                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3184                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3185 #endif
3186             } // [DM] 'else { ' deleted
3187                 if (
3188                     /* Regular tells and says */
3189                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3190                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3191                     looking_at(buf, &i, "* says: ") ||
3192                     /* Don't color "message" or "messages" output */
3193                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3194                     looking_at(buf, &i, "*. * at *:*: ") ||
3195                     looking_at(buf, &i, "--* (*:*): ") ||
3196                     /* Message notifications (same color as tells) */
3197                     looking_at(buf, &i, "* has left a message ") ||
3198                     looking_at(buf, &i, "* just sent you a message:\n") ||
3199                     /* Whispers and kibitzes */
3200                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3201                     looking_at(buf, &i, "* kibitzes: ") ||
3202                     /* Channel tells */
3203                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3204
3205                   if (tkind == 1 && strchr(star_match[0], ':')) {
3206                       /* Avoid "tells you:" spoofs in channels */
3207                      tkind = 3;
3208                   }
3209                   if (star_match[0][0] == NULLCHAR ||
3210                       strchr(star_match[0], ' ') ||
3211                       (tkind == 3 && strchr(star_match[1], ' '))) {
3212                     /* Reject bogus matches */
3213                     i = oldi;
3214                   } else {
3215                     if (appData.colorize) {
3216                       if (oldi > next_out) {
3217                         SendToPlayer(&buf[next_out], oldi - next_out);
3218                         next_out = oldi;
3219                       }
3220                       switch (tkind) {
3221                       case 1:
3222                         Colorize(ColorTell, FALSE);
3223                         curColor = ColorTell;
3224                         break;
3225                       case 2:
3226                         Colorize(ColorKibitz, FALSE);
3227                         curColor = ColorKibitz;
3228                         break;
3229                       case 3:
3230                         p = strrchr(star_match[1], '(');
3231                         if (p == NULL) {
3232                           p = star_match[1];
3233                         } else {
3234                           p++;
3235                         }
3236                         if (atoi(p) == 1) {
3237                           Colorize(ColorChannel1, FALSE);
3238                           curColor = ColorChannel1;
3239                         } else {
3240                           Colorize(ColorChannel, FALSE);
3241                           curColor = ColorChannel;
3242                         }
3243                         break;
3244                       case 5:
3245                         curColor = ColorNormal;
3246                         break;
3247                       }
3248                     }
3249                     if (started == STARTED_NONE && appData.autoComment &&
3250                         (gameMode == IcsObserving ||
3251                          gameMode == IcsPlayingWhite ||
3252                          gameMode == IcsPlayingBlack)) {
3253                       parse_pos = i - oldi;
3254                       memcpy(parse, &buf[oldi], parse_pos);
3255                       parse[parse_pos] = NULLCHAR;
3256                       started = STARTED_COMMENT;
3257                       savingComment = TRUE;
3258                     } else {
3259                       started = STARTED_CHATTER;
3260                       savingComment = FALSE;
3261                     }
3262                     loggedOn = TRUE;
3263                     continue;
3264                   }
3265                 }
3266
3267                 if (looking_at(buf, &i, "* s-shouts: ") ||
3268                     looking_at(buf, &i, "* c-shouts: ")) {
3269                     if (appData.colorize) {
3270                         if (oldi > next_out) {
3271                             SendToPlayer(&buf[next_out], oldi - next_out);
3272                             next_out = oldi;
3273                         }
3274                         Colorize(ColorSShout, FALSE);
3275                         curColor = ColorSShout;
3276                     }
3277                     loggedOn = TRUE;
3278                     started = STARTED_CHATTER;
3279                     continue;
3280                 }
3281
3282                 if (looking_at(buf, &i, "--->")) {
3283                     loggedOn = TRUE;
3284                     continue;
3285                 }
3286
3287                 if (looking_at(buf, &i, "* shouts: ") ||
3288                     looking_at(buf, &i, "--> ")) {
3289                     if (appData.colorize) {
3290                         if (oldi > next_out) {
3291                             SendToPlayer(&buf[next_out], oldi - next_out);
3292                             next_out = oldi;
3293                         }
3294                         Colorize(ColorShout, FALSE);
3295                         curColor = ColorShout;
3296                     }
3297                     loggedOn = TRUE;
3298                     started = STARTED_CHATTER;
3299                     continue;
3300                 }
3301
3302                 if (looking_at( buf, &i, "Challenge:")) {
3303                     if (appData.colorize) {
3304                         if (oldi > next_out) {
3305                             SendToPlayer(&buf[next_out], oldi - next_out);
3306                             next_out = oldi;
3307                         }
3308                         Colorize(ColorChallenge, FALSE);
3309                         curColor = ColorChallenge;
3310                     }
3311                     loggedOn = TRUE;
3312                     continue;
3313                 }
3314
3315                 if (looking_at(buf, &i, "* offers you") ||
3316                     looking_at(buf, &i, "* offers to be") ||
3317                     looking_at(buf, &i, "* would like to") ||
3318                     looking_at(buf, &i, "* requests to") ||
3319                     looking_at(buf, &i, "Your opponent offers") ||
3320                     looking_at(buf, &i, "Your opponent requests")) {
3321
3322                     if (appData.colorize) {
3323                         if (oldi > next_out) {
3324                             SendToPlayer(&buf[next_out], oldi - next_out);
3325                             next_out = oldi;
3326                         }
3327                         Colorize(ColorRequest, FALSE);
3328                         curColor = ColorRequest;
3329                     }
3330                     continue;
3331                 }
3332
3333                 if (looking_at(buf, &i, "* (*) seeking")) {
3334                     if (appData.colorize) {
3335                         if (oldi > next_out) {
3336                             SendToPlayer(&buf[next_out], oldi - next_out);
3337                             next_out = oldi;
3338                         }
3339                         Colorize(ColorSeek, FALSE);
3340                         curColor = ColorSeek;
3341                     }
3342                     continue;
3343             }
3344
3345           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3346
3347             if (looking_at(buf, &i, "\\   ")) {
3348                 if (prevColor != ColorNormal) {
3349                     if (oldi > next_out) {
3350                         SendToPlayer(&buf[next_out], oldi - next_out);
3351                         next_out = oldi;
3352                     }
3353                     Colorize(prevColor, TRUE);
3354                     curColor = prevColor;
3355                 }
3356                 if (savingComment) {
3357                     parse_pos = i - oldi;
3358                     memcpy(parse, &buf[oldi], parse_pos);
3359                     parse[parse_pos] = NULLCHAR;
3360                     started = STARTED_COMMENT;
3361                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3362                         chattingPartner = savingComment - 3; // kludge to remember the box
3363                 } else {
3364                     started = STARTED_CHATTER;
3365                 }
3366                 continue;
3367             }
3368
3369             if (looking_at(buf, &i, "Black Strength :") ||
3370                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3371                 looking_at(buf, &i, "<10>") ||
3372                 looking_at(buf, &i, "#@#")) {
3373                 /* Wrong board style */
3374                 loggedOn = TRUE;
3375                 SendToICS(ics_prefix);
3376                 SendToICS("set style 12\n");
3377                 SendToICS(ics_prefix);
3378                 SendToICS("refresh\n");
3379                 continue;
3380             }
3381
3382             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3383                 ICSInitScript();
3384                 have_sent_ICS_logon = 1;
3385                 continue;
3386             }
3387
3388             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3389                 (looking_at(buf, &i, "\n<12> ") ||
3390                  looking_at(buf, &i, "<12> "))) {
3391                 loggedOn = TRUE;
3392                 if (oldi > next_out) {
3393                     SendToPlayer(&buf[next_out], oldi - next_out);
3394                 }
3395                 next_out = i;
3396                 started = STARTED_BOARD;
3397                 parse_pos = 0;
3398                 continue;
3399             }
3400
3401             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3402                 looking_at(buf, &i, "<b1> ")) {
3403                 if (oldi > next_out) {
3404                     SendToPlayer(&buf[next_out], oldi - next_out);
3405                 }
3406                 next_out = i;
3407                 started = STARTED_HOLDINGS;
3408                 parse_pos = 0;
3409                 continue;
3410             }
3411
3412             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3413                 loggedOn = TRUE;
3414                 /* Header for a move list -- first line */
3415
3416                 switch (ics_getting_history) {
3417                   case H_FALSE:
3418                     switch (gameMode) {
3419                       case IcsIdle:
3420                       case BeginningOfGame:
3421                         /* User typed "moves" or "oldmoves" while we
3422                            were idle.  Pretend we asked for these
3423                            moves and soak them up so user can step
3424                            through them and/or save them.
3425                            */
3426                         Reset(FALSE, TRUE);
3427                         gameMode = IcsObserving;
3428                         ModeHighlight();
3429                         ics_gamenum = -1;
3430                         ics_getting_history = H_GOT_UNREQ_HEADER;
3431                         break;
3432                       case EditGame: /*?*/
3433                       case EditPosition: /*?*/
3434                         /* Should above feature work in these modes too? */
3435                         /* For now it doesn't */
3436                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3437                         break;
3438                       default:
3439                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3440                         break;
3441                     }
3442                     break;
3443                   case H_REQUESTED:
3444                     /* Is this the right one? */
3445                     if (gameInfo.white && gameInfo.black &&
3446                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3447                         strcmp(gameInfo.black, star_match[2]) == 0) {
3448                         /* All is well */
3449                         ics_getting_history = H_GOT_REQ_HEADER;
3450                     }
3451                     break;
3452                   case H_GOT_REQ_HEADER:
3453                   case H_GOT_UNREQ_HEADER:
3454                   case H_GOT_UNWANTED_HEADER:
3455                   case H_GETTING_MOVES:
3456                     /* Should not happen */
3457                     DisplayError(_("Error gathering move list: two headers"), 0);
3458                     ics_getting_history = H_FALSE;
3459                     break;
3460                 }
3461
3462                 /* Save player ratings into gameInfo if needed */
3463                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3464                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3465                     (gameInfo.whiteRating == -1 ||
3466                      gameInfo.blackRating == -1)) {
3467
3468                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3469                     gameInfo.blackRating = string_to_rating(star_match[3]);
3470                     if (appData.debugMode)
3471                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3472                               gameInfo.whiteRating, gameInfo.blackRating);
3473                 }
3474                 continue;
3475             }
3476
3477             if (looking_at(buf, &i,
3478               "* * match, initial time: * minute*, increment: * second")) {
3479                 /* Header for a move list -- second line */
3480                 /* Initial board will follow if this is a wild game */
3481                 if (gameInfo.event != NULL) free(gameInfo.event);
3482                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3483                 gameInfo.event = StrSave(str);
3484                 /* [HGM] we switched variant. Translate boards if needed. */
3485                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3486                 continue;
3487             }
3488
3489             if (looking_at(buf, &i, "Move  ")) {
3490                 /* Beginning of a move list */
3491                 switch (ics_getting_history) {
3492                   case H_FALSE:
3493                     /* Normally should not happen */
3494                     /* Maybe user hit reset while we were parsing */
3495                     break;
3496                   case H_REQUESTED:
3497                     /* Happens if we are ignoring a move list that is not
3498                      * the one we just requested.  Common if the user
3499                      * tries to observe two games without turning off
3500                      * getMoveList */
3501                     break;
3502                   case H_GETTING_MOVES:
3503                     /* Should not happen */
3504                     DisplayError(_("Error gathering move list: nested"), 0);
3505                     ics_getting_history = H_FALSE;
3506                     break;
3507                   case H_GOT_REQ_HEADER:
3508                     ics_getting_history = H_GETTING_MOVES;
3509                     started = STARTED_MOVES;
3510                     parse_pos = 0;
3511                     if (oldi > next_out) {
3512                         SendToPlayer(&buf[next_out], oldi - next_out);
3513                     }
3514                     break;
3515                   case H_GOT_UNREQ_HEADER:
3516                     ics_getting_history = H_GETTING_MOVES;
3517                     started = STARTED_MOVES_NOHIDE;
3518                     parse_pos = 0;
3519                     break;
3520                   case H_GOT_UNWANTED_HEADER:
3521                     ics_getting_history = H_FALSE;
3522                     break;
3523                 }
3524                 continue;
3525             }
3526
3527             if (looking_at(buf, &i, "% ") ||
3528                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3529                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3530                 if(soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3531                     soughtPending = FALSE;
3532                     seekGraphUp = TRUE;
3533                     DrawSeekGraph();
3534                 }
3535                 if(suppressKibitz) next_out = i;
3536                 savingComment = FALSE;
3537                 suppressKibitz = 0;
3538                 switch (started) {
3539                   case STARTED_MOVES:
3540                   case STARTED_MOVES_NOHIDE:
3541                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3542                     parse[parse_pos + i - oldi] = NULLCHAR;
3543                     ParseGameHistory(parse);
3544 #if ZIPPY
3545                     if (appData.zippyPlay && first.initDone) {
3546                         FeedMovesToProgram(&first, forwardMostMove);
3547                         if (gameMode == IcsPlayingWhite) {
3548                             if (WhiteOnMove(forwardMostMove)) {
3549                                 if (first.sendTime) {
3550                                   if (first.useColors) {
3551                                     SendToProgram("black\n", &first);
3552                                   }
3553                                   SendTimeRemaining(&first, TRUE);
3554                                 }
3555                                 if (first.useColors) {
3556                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3557                                 }
3558                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3559                                 first.maybeThinking = TRUE;
3560                             } else {
3561                                 if (first.usePlayother) {
3562                                   if (first.sendTime) {
3563                                     SendTimeRemaining(&first, TRUE);
3564                                   }
3565                                   SendToProgram("playother\n", &first);
3566                                   firstMove = FALSE;
3567                                 } else {
3568                                   firstMove = TRUE;
3569                                 }
3570                             }
3571                         } else if (gameMode == IcsPlayingBlack) {
3572                             if (!WhiteOnMove(forwardMostMove)) {
3573                                 if (first.sendTime) {
3574                                   if (first.useColors) {
3575                                     SendToProgram("white\n", &first);
3576                                   }
3577                                   SendTimeRemaining(&first, FALSE);
3578                                 }
3579                                 if (first.useColors) {
3580                                   SendToProgram("black\n", &first);
3581                                 }
3582                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3583                                 first.maybeThinking = TRUE;
3584                             } else {
3585                                 if (first.usePlayother) {
3586                                   if (first.sendTime) {
3587                                     SendTimeRemaining(&first, FALSE);
3588                                   }
3589                                   SendToProgram("playother\n", &first);
3590                                   firstMove = FALSE;
3591                                 } else {
3592                                   firstMove = TRUE;
3593                                 }
3594                             }
3595                         }
3596                     }
3597 #endif
3598                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3599                         /* Moves came from oldmoves or moves command
3600                            while we weren't doing anything else.
3601                            */
3602                         currentMove = forwardMostMove;
3603                         ClearHighlights();/*!!could figure this out*/
3604                         flipView = appData.flipView;
3605                         DrawPosition(TRUE, boards[currentMove]);
3606                         DisplayBothClocks();
3607                         snprintf(str, MSG_SIZ, "%s %s %s",
3608                                 gameInfo.white, _("vs."),  gameInfo.black);
3609                         DisplayTitle(str);
3610                         gameMode = IcsIdle;
3611                     } else {
3612                         /* Moves were history of an active game */
3613                         if (gameInfo.resultDetails != NULL) {
3614                             free(gameInfo.resultDetails);
3615                             gameInfo.resultDetails = NULL;
3616                         }
3617                     }
3618                     HistorySet(parseList, backwardMostMove,
3619                                forwardMostMove, currentMove-1);
3620                     DisplayMove(currentMove - 1);
3621                     if (started == STARTED_MOVES) next_out = i;
3622                     started = STARTED_NONE;
3623                     ics_getting_history = H_FALSE;
3624                     break;
3625
3626                   case STARTED_OBSERVE:
3627                     started = STARTED_NONE;
3628                     SendToICS(ics_prefix);
3629                     SendToICS("refresh\n");
3630                     break;
3631
3632                   default:
3633                     break;
3634                 }
3635                 if(bookHit) { // [HGM] book: simulate book reply
3636                     static char bookMove[MSG_SIZ]; // a bit generous?
3637
3638                     programStats.nodes = programStats.depth = programStats.time =
3639                     programStats.score = programStats.got_only_move = 0;
3640                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3641
3642                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3643                     strcat(bookMove, bookHit);
3644                     HandleMachineMove(bookMove, &first);
3645                 }
3646                 continue;
3647             }
3648
3649             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3650                  started == STARTED_HOLDINGS ||
3651                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3652                 /* Accumulate characters in move list or board */
3653                 parse[parse_pos++] = buf[i];
3654             }
3655
3656             /* Start of game messages.  Mostly we detect start of game
3657                when the first board image arrives.  On some versions
3658                of the ICS, though, we need to do a "refresh" after starting
3659                to observe in order to get the current board right away. */
3660             if (looking_at(buf, &i, "Adding game * to observation list")) {
3661                 started = STARTED_OBSERVE;
3662                 continue;
3663             }
3664
3665             /* Handle auto-observe */
3666             if (appData.autoObserve &&
3667                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3668                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3669                 char *player;
3670                 /* Choose the player that was highlighted, if any. */
3671                 if (star_match[0][0] == '\033' ||
3672                     star_match[1][0] != '\033') {
3673                     player = star_match[0];
3674                 } else {
3675                     player = star_match[2];
3676                 }
3677                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3678                         ics_prefix, StripHighlightAndTitle(player));
3679                 SendToICS(str);
3680
3681                 /* Save ratings from notify string */
3682                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3683                 player1Rating = string_to_rating(star_match[1]);
3684                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3685                 player2Rating = string_to_rating(star_match[3]);
3686
3687                 if (appData.debugMode)
3688                   fprintf(debugFP,
3689                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3690                           player1Name, player1Rating,
3691                           player2Name, player2Rating);
3692
3693                 continue;
3694             }
3695
3696             /* Deal with automatic examine mode after a game,
3697                and with IcsObserving -> IcsExamining transition */
3698             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3699                 looking_at(buf, &i, "has made you an examiner of game *")) {
3700
3701                 int gamenum = atoi(star_match[0]);
3702                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3703                     gamenum == ics_gamenum) {
3704                     /* We were already playing or observing this game;
3705                        no need to refetch history */
3706                     gameMode = IcsExamining;
3707                     if (pausing) {
3708                         pauseExamForwardMostMove = forwardMostMove;
3709                     } else if (currentMove < forwardMostMove) {
3710                         ForwardInner(forwardMostMove);
3711                     }
3712                 } else {
3713                     /* I don't think this case really can happen */
3714                     SendToICS(ics_prefix);
3715                     SendToICS("refresh\n");
3716                 }
3717                 continue;
3718             }
3719
3720             /* Error messages */
3721 //          if (ics_user_moved) {
3722             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3723                 if (looking_at(buf, &i, "Illegal move") ||
3724                     looking_at(buf, &i, "Not a legal move") ||
3725                     looking_at(buf, &i, "Your king is in check") ||
3726                     looking_at(buf, &i, "It isn't your turn") ||
3727                     looking_at(buf, &i, "It is not your move")) {
3728                     /* Illegal move */
3729                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3730                         currentMove = forwardMostMove-1;
3731                         DisplayMove(currentMove - 1); /* before DMError */
3732                         DrawPosition(FALSE, boards[currentMove]);
3733                         SwitchClocks(forwardMostMove-1); // [HGM] race
3734                         DisplayBothClocks();
3735                     }
3736                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3737                     ics_user_moved = 0;
3738                     continue;
3739                 }
3740             }
3741
3742             if (looking_at(buf, &i, "still have time") ||
3743                 looking_at(buf, &i, "not out of time") ||
3744                 looking_at(buf, &i, "either player is out of time") ||
3745                 looking_at(buf, &i, "has timeseal; checking")) {
3746                 /* We must have called his flag a little too soon */
3747                 whiteFlag = blackFlag = FALSE;
3748                 continue;
3749             }
3750
3751             if (looking_at(buf, &i, "added * seconds to") ||
3752                 looking_at(buf, &i, "seconds were added to")) {
3753                 /* Update the clocks */
3754                 SendToICS(ics_prefix);
3755                 SendToICS("refresh\n");
3756                 continue;
3757             }
3758
3759             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3760                 ics_clock_paused = TRUE;
3761                 StopClocks();
3762                 continue;
3763             }
3764
3765             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3766                 ics_clock_paused = FALSE;
3767                 StartClocks();
3768                 continue;
3769             }
3770
3771             /* Grab player ratings from the Creating: message.
3772                Note we have to check for the special case when
3773                the ICS inserts things like [white] or [black]. */
3774             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3775                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3776                 /* star_matches:
3777                    0    player 1 name (not necessarily white)
3778                    1    player 1 rating
3779                    2    empty, white, or black (IGNORED)
3780                    3    player 2 name (not necessarily black)
3781                    4    player 2 rating
3782
3783                    The names/ratings are sorted out when the game
3784                    actually starts (below).
3785                 */
3786                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3787                 player1Rating = string_to_rating(star_match[1]);
3788                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3789                 player2Rating = string_to_rating(star_match[4]);
3790
3791                 if (appData.debugMode)
3792                   fprintf(debugFP,
3793                           "Ratings from 'Creating:' %s %d, %s %d\n",
3794                           player1Name, player1Rating,
3795                           player2Name, player2Rating);
3796
3797                 continue;
3798             }
3799
3800             /* Improved generic start/end-of-game messages */
3801             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3802                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3803                 /* If tkind == 0: */
3804                 /* star_match[0] is the game number */
3805                 /*           [1] is the white player's name */
3806                 /*           [2] is the black player's name */
3807                 /* For end-of-game: */
3808                 /*           [3] is the reason for the game end */
3809                 /*           [4] is a PGN end game-token, preceded by " " */
3810                 /* For start-of-game: */
3811                 /*           [3] begins with "Creating" or "Continuing" */
3812                 /*           [4] is " *" or empty (don't care). */
3813                 int gamenum = atoi(star_match[0]);
3814                 char *whitename, *blackname, *why, *endtoken;
3815                 ChessMove endtype = EndOfFile;
3816
3817                 if (tkind == 0) {
3818                   whitename = star_match[1];
3819                   blackname = star_match[2];
3820                   why = star_match[3];
3821                   endtoken = star_match[4];
3822                 } else {
3823                   whitename = star_match[1];
3824                   blackname = star_match[3];
3825                   why = star_match[5];
3826                   endtoken = star_match[6];
3827                 }
3828
3829                 /* Game start messages */
3830                 if (strncmp(why, "Creating ", 9) == 0 ||
3831                     strncmp(why, "Continuing ", 11) == 0) {
3832                     gs_gamenum = gamenum;
3833                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3834                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3835                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3836 #if ZIPPY
3837                     if (appData.zippyPlay) {
3838                         ZippyGameStart(whitename, blackname);
3839                     }
3840 #endif /*ZIPPY*/
3841                     partnerBoardValid = FALSE; // [HGM] bughouse
3842                     continue;
3843                 }
3844
3845                 /* Game end messages */
3846                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3847                     ics_gamenum != gamenum) {
3848                     continue;
3849                 }
3850                 while (endtoken[0] == ' ') endtoken++;
3851                 switch (endtoken[0]) {
3852                   case '*':
3853                   default:
3854                     endtype = GameUnfinished;
3855                     break;
3856                   case '0':
3857                     endtype = BlackWins;
3858                     break;
3859                   case '1':
3860                     if (endtoken[1] == '/')
3861                       endtype = GameIsDrawn;
3862                     else
3863                       endtype = WhiteWins;
3864                     break;
3865                 }
3866                 GameEnds(endtype, why, GE_ICS);
3867 #if ZIPPY
3868                 if (appData.zippyPlay && first.initDone) {
3869                     ZippyGameEnd(endtype, why);
3870                     if (first.pr == NoProc) {
3871                       /* Start the next process early so that we'll
3872                          be ready for the next challenge */
3873                       StartChessProgram(&first);
3874                     }
3875                     /* Send "new" early, in case this command takes
3876                        a long time to finish, so that we'll be ready
3877                        for the next challenge. */
3878                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3879                     Reset(TRUE, TRUE);
3880                 }
3881 #endif /*ZIPPY*/
3882                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3883                 continue;
3884             }
3885
3886             if (looking_at(buf, &i, "Removing game * from observation") ||
3887                 looking_at(buf, &i, "no longer observing game *") ||
3888                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3889                 if (gameMode == IcsObserving &&
3890                     atoi(star_match[0]) == ics_gamenum)
3891                   {
3892                       /* icsEngineAnalyze */
3893                       if (appData.icsEngineAnalyze) {
3894                             ExitAnalyzeMode();
3895                             ModeHighlight();
3896                       }
3897                       StopClocks();
3898                       gameMode = IcsIdle;
3899                       ics_gamenum = -1;
3900                       ics_user_moved = FALSE;
3901                   }
3902                 continue;
3903             }
3904
3905             if (looking_at(buf, &i, "no longer examining game *")) {
3906                 if (gameMode == IcsExamining &&
3907                     atoi(star_match[0]) == ics_gamenum)
3908                   {
3909                       gameMode = IcsIdle;
3910                       ics_gamenum = -1;
3911                       ics_user_moved = FALSE;
3912                   }
3913                 continue;
3914             }
3915
3916             /* Advance leftover_start past any newlines we find,
3917                so only partial lines can get reparsed */
3918             if (looking_at(buf, &i, "\n")) {
3919                 prevColor = curColor;
3920                 if (curColor != ColorNormal) {
3921                     if (oldi > next_out) {
3922                         SendToPlayer(&buf[next_out], oldi - next_out);
3923                         next_out = oldi;
3924                     }
3925                     Colorize(ColorNormal, FALSE);
3926                     curColor = ColorNormal;
3927                 }
3928                 if (started == STARTED_BOARD) {
3929                     started = STARTED_NONE;
3930                     parse[parse_pos] = NULLCHAR;
3931                     ParseBoard12(parse);
3932                     ics_user_moved = 0;
3933
3934                     /* Send premove here */
3935                     if (appData.premove) {
3936                       char str[MSG_SIZ];
3937                       if (currentMove == 0 &&
3938                           gameMode == IcsPlayingWhite &&
3939                           appData.premoveWhite) {
3940                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3941                         if (appData.debugMode)
3942                           fprintf(debugFP, "Sending premove:\n");
3943                         SendToICS(str);
3944                       } else if (currentMove == 1 &&
3945                                  gameMode == IcsPlayingBlack &&
3946                                  appData.premoveBlack) {
3947                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3948                         if (appData.debugMode)
3949                           fprintf(debugFP, "Sending premove:\n");
3950                         SendToICS(str);
3951                       } else if (gotPremove) {
3952                         gotPremove = 0;
3953                         ClearPremoveHighlights();
3954                         if (appData.debugMode)
3955                           fprintf(debugFP, "Sending premove:\n");
3956                           UserMoveEvent(premoveFromX, premoveFromY,
3957                                         premoveToX, premoveToY,
3958                                         premovePromoChar);
3959                       }
3960                     }
3961
3962                     /* Usually suppress following prompt */
3963                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3964                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3965                         if (looking_at(buf, &i, "*% ")) {
3966                             savingComment = FALSE;
3967                             suppressKibitz = 0;
3968                         }
3969                     }
3970                     next_out = i;
3971                 } else if (started == STARTED_HOLDINGS) {
3972                     int gamenum;
3973                     char new_piece[MSG_SIZ];
3974                     started = STARTED_NONE;
3975                     parse[parse_pos] = NULLCHAR;
3976                     if (appData.debugMode)
3977                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3978                                                         parse, currentMove);
3979                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3980                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3981                         if (gameInfo.variant == VariantNormal) {
3982                           /* [HGM] We seem to switch variant during a game!
3983                            * Presumably no holdings were displayed, so we have
3984                            * to move the position two files to the right to
3985                            * create room for them!
3986                            */
3987                           VariantClass newVariant;
3988                           switch(gameInfo.boardWidth) { // base guess on board width
3989                                 case 9:  newVariant = VariantShogi; break;
3990                                 case 10: newVariant = VariantGreat; break;
3991                                 default: newVariant = VariantCrazyhouse; break;
3992                           }
3993                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3994                           /* Get a move list just to see the header, which
3995                              will tell us whether this is really bug or zh */
3996                           if (ics_getting_history == H_FALSE) {
3997                             ics_getting_history = H_REQUESTED;
3998                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3999                             SendToICS(str);
4000                           }
4001                         }
4002                         new_piece[0] = NULLCHAR;
4003                         sscanf(parse, "game %d white [%s black [%s <- %s",
4004                                &gamenum, white_holding, black_holding,
4005                                new_piece);
4006                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4007                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4008                         /* [HGM] copy holdings to board holdings area */
4009                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4010                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4011                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4012 #if ZIPPY
4013                         if (appData.zippyPlay && first.initDone) {
4014                             ZippyHoldings(white_holding, black_holding,
4015                                           new_piece);
4016                         }
4017 #endif /*ZIPPY*/
4018                         if (tinyLayout || smallLayout) {
4019                             char wh[16], bh[16];
4020                             PackHolding(wh, white_holding);
4021                             PackHolding(bh, black_holding);
4022                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4023                                     gameInfo.white, gameInfo.black);
4024                         } else {
4025                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4026                                     gameInfo.white, white_holding, _("vs."),
4027                                     gameInfo.black, black_holding);
4028                         }
4029                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4030                         DrawPosition(FALSE, boards[currentMove]);
4031                         DisplayTitle(str);
4032                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4033                         sscanf(parse, "game %d white [%s black [%s <- %s",
4034                                &gamenum, white_holding, black_holding,
4035                                new_piece);
4036                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4037                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4038                         /* [HGM] copy holdings to partner-board holdings area */
4039                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4040                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4041                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4042                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4043                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4044                       }
4045                     }
4046                     /* Suppress following prompt */
4047                     if (looking_at(buf, &i, "*% ")) {
4048                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4049                         savingComment = FALSE;
4050                         suppressKibitz = 0;
4051                     }
4052                     next_out = i;
4053                 }
4054                 continue;
4055             }
4056
4057             i++;                /* skip unparsed character and loop back */
4058         }
4059
4060         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4061 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4062 //          SendToPlayer(&buf[next_out], i - next_out);
4063             started != STARTED_HOLDINGS && leftover_start > next_out) {
4064             SendToPlayer(&buf[next_out], leftover_start - next_out);
4065             next_out = i;
4066         }
4067
4068         leftover_len = buf_len - leftover_start;
4069         /* if buffer ends with something we couldn't parse,
4070            reparse it after appending the next read */
4071
4072     } else if (count == 0) {
4073         RemoveInputSource(isr);
4074         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4075     } else {
4076         DisplayFatalError(_("Error reading from ICS"), error, 1);
4077     }
4078 }
4079
4080
4081 /* Board style 12 looks like this:
4082
4083    <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
4084
4085  * The "<12> " is stripped before it gets to this routine.  The two
4086  * trailing 0's (flip state and clock ticking) are later addition, and
4087  * some chess servers may not have them, or may have only the first.
4088  * Additional trailing fields may be added in the future.
4089  */
4090
4091 #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"
4092
4093 #define RELATION_OBSERVING_PLAYED    0
4094 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4095 #define RELATION_PLAYING_MYMOVE      1
4096 #define RELATION_PLAYING_NOTMYMOVE  -1
4097 #define RELATION_EXAMINING           2
4098 #define RELATION_ISOLATED_BOARD     -3
4099 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4100
4101 void
4102 ParseBoard12 (char *string)
4103 {
4104     GameMode newGameMode;
4105     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4106     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4107     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4108     char to_play, board_chars[200];
4109     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4110     char black[32], white[32];
4111     Board board;
4112     int prevMove = currentMove;
4113     int ticking = 2;
4114     ChessMove moveType;
4115     int fromX, fromY, toX, toY;
4116     char promoChar;
4117     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4118     char *bookHit = NULL; // [HGM] book
4119     Boolean weird = FALSE, reqFlag = FALSE;
4120
4121     fromX = fromY = toX = toY = -1;
4122
4123     newGame = FALSE;
4124
4125     if (appData.debugMode)
4126       fprintf(debugFP, _("Parsing board: %s\n"), string);
4127
4128     move_str[0] = NULLCHAR;
4129     elapsed_time[0] = NULLCHAR;
4130     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4131         int  i = 0, j;
4132         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4133             if(string[i] == ' ') { ranks++; files = 0; }
4134             else files++;
4135             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4136             i++;
4137         }
4138         for(j = 0; j <i; j++) board_chars[j] = string[j];
4139         board_chars[i] = '\0';
4140         string += i + 1;
4141     }
4142     n = sscanf(string, PATTERN, &to_play, &double_push,
4143                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4144                &gamenum, white, black, &relation, &basetime, &increment,
4145                &white_stren, &black_stren, &white_time, &black_time,
4146                &moveNum, str, elapsed_time, move_str, &ics_flip,
4147                &ticking);
4148
4149     if (n < 21) {
4150         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4151         DisplayError(str, 0);
4152         return;
4153     }
4154
4155     /* Convert the move number to internal form */
4156     moveNum = (moveNum - 1) * 2;
4157     if (to_play == 'B') moveNum++;
4158     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4159       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4160                         0, 1);
4161       return;
4162     }
4163
4164     switch (relation) {
4165       case RELATION_OBSERVING_PLAYED:
4166       case RELATION_OBSERVING_STATIC:
4167         if (gamenum == -1) {
4168             /* Old ICC buglet */
4169             relation = RELATION_OBSERVING_STATIC;
4170         }
4171         newGameMode = IcsObserving;
4172         break;
4173       case RELATION_PLAYING_MYMOVE:
4174       case RELATION_PLAYING_NOTMYMOVE:
4175         newGameMode =
4176           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4177             IcsPlayingWhite : IcsPlayingBlack;
4178         break;
4179       case RELATION_EXAMINING:
4180         newGameMode = IcsExamining;
4181         break;
4182       case RELATION_ISOLATED_BOARD:
4183       default:
4184         /* Just display this board.  If user was doing something else,
4185            we will forget about it until the next board comes. */
4186         newGameMode = IcsIdle;
4187         break;
4188       case RELATION_STARTING_POSITION:
4189         newGameMode = gameMode;
4190         break;
4191     }
4192
4193     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4194          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4195       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4196       char *toSqr;
4197       for (k = 0; k < ranks; k++) {
4198         for (j = 0; j < files; j++)
4199           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4200         if(gameInfo.holdingsWidth > 1) {
4201              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4202              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4203         }
4204       }
4205       CopyBoard(partnerBoard, board);
4206       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4207         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4208         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4209       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4210       if(toSqr = strchr(str, '-')) {
4211         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4212         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4213       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4214       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4215       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4216       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4217       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4218       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4219                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4220       DisplayMessage(partnerStatus, "");
4221         partnerBoardValid = TRUE;
4222       return;
4223     }
4224
4225     /* Modify behavior for initial board display on move listing
4226        of wild games.
4227        */
4228     switch (ics_getting_history) {
4229       case H_FALSE:
4230       case H_REQUESTED:
4231         break;
4232       case H_GOT_REQ_HEADER:
4233       case H_GOT_UNREQ_HEADER:
4234         /* This is the initial position of the current game */
4235         gamenum = ics_gamenum;
4236         moveNum = 0;            /* old ICS bug workaround */
4237         if (to_play == 'B') {
4238           startedFromSetupPosition = TRUE;
4239           blackPlaysFirst = TRUE;
4240           moveNum = 1;
4241           if (forwardMostMove == 0) forwardMostMove = 1;
4242           if (backwardMostMove == 0) backwardMostMove = 1;
4243           if (currentMove == 0) currentMove = 1;
4244         }
4245         newGameMode = gameMode;
4246         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4247         break;
4248       case H_GOT_UNWANTED_HEADER:
4249         /* This is an initial board that we don't want */
4250         return;
4251       case H_GETTING_MOVES:
4252         /* Should not happen */
4253         DisplayError(_("Error gathering move list: extra board"), 0);
4254         ics_getting_history = H_FALSE;
4255         return;
4256     }
4257
4258    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4259                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4260      /* [HGM] We seem to have switched variant unexpectedly
4261       * Try to guess new variant from board size
4262       */
4263           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4264           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4265           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4266           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4267           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4268           if(!weird) newVariant = VariantNormal;
4269           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4270           /* Get a move list just to see the header, which
4271              will tell us whether this is really bug or zh */
4272           if (ics_getting_history == H_FALSE) {
4273             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4274             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4275             SendToICS(str);
4276           }
4277     }
4278
4279     /* Take action if this is the first board of a new game, or of a
4280        different game than is currently being displayed.  */
4281     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4282         relation == RELATION_ISOLATED_BOARD) {
4283
4284         /* Forget the old game and get the history (if any) of the new one */
4285         if (gameMode != BeginningOfGame) {
4286           Reset(TRUE, TRUE);
4287         }
4288         newGame = TRUE;
4289         if (appData.autoRaiseBoard) BoardToTop();
4290         prevMove = -3;
4291         if (gamenum == -1) {
4292             newGameMode = IcsIdle;
4293         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4294                    appData.getMoveList && !reqFlag) {
4295             /* Need to get game history */
4296             ics_getting_history = H_REQUESTED;
4297             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4298             SendToICS(str);
4299         }
4300
4301         /* Initially flip the board to have black on the bottom if playing
4302            black or if the ICS flip flag is set, but let the user change
4303            it with the Flip View button. */
4304         flipView = appData.autoFlipView ?
4305           (newGameMode == IcsPlayingBlack) || ics_flip :
4306           appData.flipView;
4307
4308         /* Done with values from previous mode; copy in new ones */
4309         gameMode = newGameMode;
4310         ModeHighlight();
4311         ics_gamenum = gamenum;
4312         if (gamenum == gs_gamenum) {
4313             int klen = strlen(gs_kind);
4314             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4315             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4316             gameInfo.event = StrSave(str);
4317         } else {
4318             gameInfo.event = StrSave("ICS game");
4319         }
4320         gameInfo.site = StrSave(appData.icsHost);
4321         gameInfo.date = PGNDate();
4322         gameInfo.round = StrSave("-");
4323         gameInfo.white = StrSave(white);
4324         gameInfo.black = StrSave(black);
4325         timeControl = basetime * 60 * 1000;
4326         timeControl_2 = 0;
4327         timeIncrement = increment * 1000;
4328         movesPerSession = 0;
4329         gameInfo.timeControl = TimeControlTagValue();
4330         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4331   if (appData.debugMode) {
4332     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4333     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4334     setbuf(debugFP, NULL);
4335   }
4336
4337         gameInfo.outOfBook = NULL;
4338
4339         /* Do we have the ratings? */
4340         if (strcmp(player1Name, white) == 0 &&
4341             strcmp(player2Name, black) == 0) {
4342             if (appData.debugMode)
4343               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4344                       player1Rating, player2Rating);
4345             gameInfo.whiteRating = player1Rating;
4346             gameInfo.blackRating = player2Rating;
4347         } else if (strcmp(player2Name, white) == 0 &&
4348                    strcmp(player1Name, black) == 0) {
4349             if (appData.debugMode)
4350               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4351                       player2Rating, player1Rating);
4352             gameInfo.whiteRating = player2Rating;
4353             gameInfo.blackRating = player1Rating;
4354         }
4355         player1Name[0] = player2Name[0] = NULLCHAR;
4356
4357         /* Silence shouts if requested */
4358         if (appData.quietPlay &&
4359             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4360             SendToICS(ics_prefix);
4361             SendToICS("set shout 0\n");
4362         }
4363     }
4364
4365     /* Deal with midgame name changes */
4366     if (!newGame) {
4367         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4368             if (gameInfo.white) free(gameInfo.white);
4369             gameInfo.white = StrSave(white);
4370         }
4371         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4372             if (gameInfo.black) free(gameInfo.black);
4373             gameInfo.black = StrSave(black);
4374         }
4375     }
4376
4377     /* Throw away game result if anything actually changes in examine mode */
4378     if (gameMode == IcsExamining && !newGame) {
4379         gameInfo.result = GameUnfinished;
4380         if (gameInfo.resultDetails != NULL) {
4381             free(gameInfo.resultDetails);
4382             gameInfo.resultDetails = NULL;
4383         }
4384     }
4385
4386     /* In pausing && IcsExamining mode, we ignore boards coming
4387        in if they are in a different variation than we are. */
4388     if (pauseExamInvalid) return;
4389     if (pausing && gameMode == IcsExamining) {
4390         if (moveNum <= pauseExamForwardMostMove) {
4391             pauseExamInvalid = TRUE;
4392             forwardMostMove = pauseExamForwardMostMove;
4393             return;
4394         }
4395     }
4396
4397   if (appData.debugMode) {
4398     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4399   }
4400     /* Parse the board */
4401     for (k = 0; k < ranks; k++) {
4402       for (j = 0; j < files; j++)
4403         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4404       if(gameInfo.holdingsWidth > 1) {
4405            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4406            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4407       }
4408     }
4409     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4410       board[5][BOARD_RGHT+1] = WhiteAngel;
4411       board[6][BOARD_RGHT+1] = WhiteMarshall;
4412       board[1][0] = BlackMarshall;
4413       board[2][0] = BlackAngel;
4414       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4415     }
4416     CopyBoard(boards[moveNum], board);
4417     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4418     if (moveNum == 0) {
4419         startedFromSetupPosition =
4420           !CompareBoards(board, initialPosition);
4421         if(startedFromSetupPosition)
4422             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4423     }
4424
4425     /* [HGM] Set castling rights. Take the outermost Rooks,
4426        to make it also work for FRC opening positions. Note that board12
4427        is really defective for later FRC positions, as it has no way to
4428        indicate which Rook can castle if they are on the same side of King.
4429        For the initial position we grant rights to the outermost Rooks,
4430        and remember thos rights, and we then copy them on positions
4431        later in an FRC game. This means WB might not recognize castlings with
4432        Rooks that have moved back to their original position as illegal,
4433        but in ICS mode that is not its job anyway.
4434     */
4435     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4436     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4437
4438         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4439             if(board[0][i] == WhiteRook) j = i;
4440         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4441         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4442             if(board[0][i] == WhiteRook) j = i;
4443         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4444         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4445             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4446         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4447         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4448             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4449         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4450
4451         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4452         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4453         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4454             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4455         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4456             if(board[BOARD_HEIGHT-1][k] == bKing)
4457                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4458         if(gameInfo.variant == VariantTwoKings) {
4459             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4460             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4461             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4462         }
4463     } else { int r;
4464         r = boards[moveNum][CASTLING][0] = initialRights[0];
4465         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4466         r = boards[moveNum][CASTLING][1] = initialRights[1];
4467         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4468         r = boards[moveNum][CASTLING][3] = initialRights[3];
4469         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4470         r = boards[moveNum][CASTLING][4] = initialRights[4];
4471         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4472         /* wildcastle kludge: always assume King has rights */
4473         r = boards[moveNum][CASTLING][2] = initialRights[2];
4474         r = boards[moveNum][CASTLING][5] = initialRights[5];
4475     }
4476     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4477     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4478
4479
4480     if (ics_getting_history == H_GOT_REQ_HEADER ||
4481         ics_getting_history == H_GOT_UNREQ_HEADER) {
4482         /* This was an initial position from a move list, not
4483            the current position */
4484         return;
4485     }
4486
4487     /* Update currentMove and known move number limits */
4488     newMove = newGame || moveNum > forwardMostMove;
4489
4490     if (newGame) {
4491         forwardMostMove = backwardMostMove = currentMove = moveNum;
4492         if (gameMode == IcsExamining && moveNum == 0) {
4493           /* Workaround for ICS limitation: we are not told the wild
4494              type when starting to examine a game.  But if we ask for
4495              the move list, the move list header will tell us */
4496             ics_getting_history = H_REQUESTED;
4497             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4498             SendToICS(str);
4499         }
4500     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4501                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4502 #if ZIPPY
4503         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4504         /* [HGM] applied this also to an engine that is silently watching        */
4505         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4506             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4507             gameInfo.variant == currentlyInitializedVariant) {
4508           takeback = forwardMostMove - moveNum;
4509           for (i = 0; i < takeback; i++) {
4510             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4511             SendToProgram("undo\n", &first);
4512           }
4513         }
4514 #endif
4515
4516         forwardMostMove = moveNum;
4517         if (!pausing || currentMove > forwardMostMove)
4518           currentMove = forwardMostMove;
4519     } else {
4520         /* New part of history that is not contiguous with old part */
4521         if (pausing && gameMode == IcsExamining) {
4522             pauseExamInvalid = TRUE;
4523             forwardMostMove = pauseExamForwardMostMove;
4524             return;
4525         }
4526         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4527 #if ZIPPY
4528             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4529                 // [HGM] when we will receive the move list we now request, it will be
4530                 // fed to the engine from the first move on. So if the engine is not
4531                 // in the initial position now, bring it there.
4532                 InitChessProgram(&first, 0);
4533             }
4534 #endif
4535             ics_getting_history = H_REQUESTED;
4536             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4537             SendToICS(str);
4538         }
4539         forwardMostMove = backwardMostMove = currentMove = moveNum;
4540     }
4541
4542     /* Update the clocks */
4543     if (strchr(elapsed_time, '.')) {
4544       /* Time is in ms */
4545       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4546       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4547     } else {
4548       /* Time is in seconds */
4549       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4550       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4551     }
4552
4553
4554 #if ZIPPY
4555     if (appData.zippyPlay && newGame &&
4556         gameMode != IcsObserving && gameMode != IcsIdle &&
4557         gameMode != IcsExamining)
4558       ZippyFirstBoard(moveNum, basetime, increment);
4559 #endif
4560
4561     /* Put the move on the move list, first converting
4562        to canonical algebraic form. */
4563     if (moveNum > 0) {
4564   if (appData.debugMode) {
4565     if (appData.debugMode) { int f = forwardMostMove;
4566         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4567                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4568                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4569     }
4570     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4571     fprintf(debugFP, "moveNum = %d\n", moveNum);
4572     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4573     setbuf(debugFP, NULL);
4574   }
4575         if (moveNum <= backwardMostMove) {
4576             /* We don't know what the board looked like before
4577                this move.  Punt. */
4578           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4579             strcat(parseList[moveNum - 1], " ");
4580             strcat(parseList[moveNum - 1], elapsed_time);
4581             moveList[moveNum - 1][0] = NULLCHAR;
4582         } else if (strcmp(move_str, "none") == 0) {
4583             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4584             /* Again, we don't know what the board looked like;
4585                this is really the start of the game. */
4586             parseList[moveNum - 1][0] = NULLCHAR;
4587             moveList[moveNum - 1][0] = NULLCHAR;
4588             backwardMostMove = moveNum;
4589             startedFromSetupPosition = TRUE;
4590             fromX = fromY = toX = toY = -1;
4591         } else {
4592           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4593           //                 So we parse the long-algebraic move string in stead of the SAN move
4594           int valid; char buf[MSG_SIZ], *prom;
4595
4596           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4597                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4598           // str looks something like "Q/a1-a2"; kill the slash
4599           if(str[1] == '/')
4600             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4601           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4602           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4603                 strcat(buf, prom); // long move lacks promo specification!
4604           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4605                 if(appData.debugMode)
4606                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4607                 safeStrCpy(move_str, buf, MSG_SIZ);
4608           }
4609           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4610                                 &fromX, &fromY, &toX, &toY, &promoChar)
4611                || ParseOneMove(buf, moveNum - 1, &moveType,
4612                                 &fromX, &fromY, &toX, &toY, &promoChar);
4613           // end of long SAN patch
4614           if (valid) {
4615             (void) CoordsToAlgebraic(boards[moveNum - 1],
4616                                      PosFlags(moveNum - 1),
4617                                      fromY, fromX, toY, toX, promoChar,
4618                                      parseList[moveNum-1]);
4619             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4620               case MT_NONE:
4621               case MT_STALEMATE:
4622               default:
4623                 break;
4624               case MT_CHECK:
4625                 if(gameInfo.variant != VariantShogi)
4626                     strcat(parseList[moveNum - 1], "+");
4627                 break;
4628               case MT_CHECKMATE:
4629               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4630                 strcat(parseList[moveNum - 1], "#");
4631                 break;
4632             }
4633             strcat(parseList[moveNum - 1], " ");
4634             strcat(parseList[moveNum - 1], elapsed_time);
4635             /* currentMoveString is set as a side-effect of ParseOneMove */
4636             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4637             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4638             strcat(moveList[moveNum - 1], "\n");
4639
4640             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4641                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4642               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4643                 ChessSquare old, new = boards[moveNum][k][j];
4644                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4645                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4646                   if(old == new) continue;
4647                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4648                   else if(new == WhiteWazir || new == BlackWazir) {
4649                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4650                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4651                       else boards[moveNum][k][j] = old; // preserve type of Gold
4652                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4653                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4654               }
4655           } else {
4656             /* Move from ICS was illegal!?  Punt. */
4657             if (appData.debugMode) {
4658               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4659               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4660             }
4661             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4662             strcat(parseList[moveNum - 1], " ");
4663             strcat(parseList[moveNum - 1], elapsed_time);
4664             moveList[moveNum - 1][0] = NULLCHAR;
4665             fromX = fromY = toX = toY = -1;
4666           }
4667         }
4668   if (appData.debugMode) {
4669     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4670     setbuf(debugFP, NULL);
4671   }
4672
4673 #if ZIPPY
4674         /* Send move to chess program (BEFORE animating it). */
4675         if (appData.zippyPlay && !newGame && newMove &&
4676            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4677
4678             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4679                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4680                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4681                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4682                             move_str);
4683                     DisplayError(str, 0);
4684                 } else {
4685                     if (first.sendTime) {
4686                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4687                     }
4688                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4689                     if (firstMove && !bookHit) {
4690                         firstMove = FALSE;
4691                         if (first.useColors) {
4692                           SendToProgram(gameMode == IcsPlayingWhite ?
4693                                         "white\ngo\n" :
4694                                         "black\ngo\n", &first);
4695                         } else {
4696                           SendToProgram("go\n", &first);
4697                         }
4698                         first.maybeThinking = TRUE;
4699                     }
4700                 }
4701             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4702               if (moveList[moveNum - 1][0] == NULLCHAR) {
4703                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4704                 DisplayError(str, 0);
4705               } else {
4706                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4707                 SendMoveToProgram(moveNum - 1, &first);
4708               }
4709             }
4710         }
4711 #endif
4712     }
4713
4714     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4715         /* If move comes from a remote source, animate it.  If it
4716            isn't remote, it will have already been animated. */
4717         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4718             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4719         }
4720         if (!pausing && appData.highlightLastMove) {
4721             SetHighlights(fromX, fromY, toX, toY);
4722         }
4723     }
4724
4725     /* Start the clocks */
4726     whiteFlag = blackFlag = FALSE;
4727     appData.clockMode = !(basetime == 0 && increment == 0);
4728     if (ticking == 0) {
4729       ics_clock_paused = TRUE;
4730       StopClocks();
4731     } else if (ticking == 1) {
4732       ics_clock_paused = FALSE;
4733     }
4734     if (gameMode == IcsIdle ||
4735         relation == RELATION_OBSERVING_STATIC ||
4736         relation == RELATION_EXAMINING ||
4737         ics_clock_paused)
4738       DisplayBothClocks();
4739     else
4740       StartClocks();
4741
4742     /* Display opponents and material strengths */
4743     if (gameInfo.variant != VariantBughouse &&
4744         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4745         if (tinyLayout || smallLayout) {
4746             if(gameInfo.variant == VariantNormal)
4747               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4748                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4749                     basetime, increment);
4750             else
4751               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4752                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4753                     basetime, increment, (int) gameInfo.variant);
4754         } else {
4755             if(gameInfo.variant == VariantNormal)
4756               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4757                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4758                     basetime, increment);
4759             else
4760               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4761                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4762                     basetime, increment, VariantName(gameInfo.variant));
4763         }
4764         DisplayTitle(str);
4765   if (appData.debugMode) {
4766     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4767   }
4768     }
4769
4770
4771     /* Display the board */
4772     if (!pausing && !appData.noGUI) {
4773
4774       if (appData.premove)
4775           if (!gotPremove ||
4776              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4777              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4778               ClearPremoveHighlights();
4779
4780       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4781         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4782       DrawPosition(j, boards[currentMove]);
4783
4784       DisplayMove(moveNum - 1);
4785       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4786             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4787               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4788         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4789       }
4790     }
4791
4792     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4793 #if ZIPPY
4794     if(bookHit) { // [HGM] book: simulate book reply
4795         static char bookMove[MSG_SIZ]; // a bit generous?
4796
4797         programStats.nodes = programStats.depth = programStats.time =
4798         programStats.score = programStats.got_only_move = 0;
4799         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4800
4801         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4802         strcat(bookMove, bookHit);
4803         HandleMachineMove(bookMove, &first);
4804     }
4805 #endif
4806 }
4807
4808 void
4809 GetMoveListEvent ()
4810 {
4811     char buf[MSG_SIZ];
4812     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4813         ics_getting_history = H_REQUESTED;
4814         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4815         SendToICS(buf);
4816     }
4817 }
4818
4819 void
4820 AnalysisPeriodicEvent (int force)
4821 {
4822     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4823          && !force) || !appData.periodicUpdates)
4824       return;
4825
4826     /* Send . command to Crafty to collect stats */
4827     SendToProgram(".\n", &first);
4828
4829     /* Don't send another until we get a response (this makes
4830        us stop sending to old Crafty's which don't understand
4831        the "." command (sending illegal cmds resets node count & time,
4832        which looks bad)) */
4833     programStats.ok_to_send = 0;
4834 }
4835
4836 void
4837 ics_update_width (int new_width)
4838 {
4839         ics_printf("set width %d\n", new_width);
4840 }
4841
4842 void
4843 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4844 {
4845     char buf[MSG_SIZ];
4846
4847     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4848         // null move in variant where engine does not understand it (for analysis purposes)
4849         SendBoard(cps, moveNum + 1); // send position after move in stead.
4850         return;
4851     }
4852     if (cps->useUsermove) {
4853       SendToProgram("usermove ", cps);
4854     }
4855     if (cps->useSAN) {
4856       char *space;
4857       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4858         int len = space - parseList[moveNum];
4859         memcpy(buf, parseList[moveNum], len);
4860         buf[len++] = '\n';
4861         buf[len] = NULLCHAR;
4862       } else {
4863         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4864       }
4865       SendToProgram(buf, cps);
4866     } else {
4867       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4868         AlphaRank(moveList[moveNum], 4);
4869         SendToProgram(moveList[moveNum], cps);
4870         AlphaRank(moveList[moveNum], 4); // and back
4871       } else
4872       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4873        * the engine. It would be nice to have a better way to identify castle
4874        * moves here. */
4875       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4876                                                                          && cps->useOOCastle) {
4877         int fromX = moveList[moveNum][0] - AAA;
4878         int fromY = moveList[moveNum][1] - ONE;
4879         int toX = moveList[moveNum][2] - AAA;
4880         int toY = moveList[moveNum][3] - ONE;
4881         if((boards[moveNum][fromY][fromX] == WhiteKing
4882             && boards[moveNum][toY][toX] == WhiteRook)
4883            || (boards[moveNum][fromY][fromX] == BlackKing
4884                && boards[moveNum][toY][toX] == BlackRook)) {
4885           if(toX > fromX) SendToProgram("O-O\n", cps);
4886           else SendToProgram("O-O-O\n", cps);
4887         }
4888         else SendToProgram(moveList[moveNum], cps);
4889       } else
4890       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4891         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4892           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4893           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4894                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4895         } else
4896           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4897                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4898         SendToProgram(buf, cps);
4899       }
4900       else SendToProgram(moveList[moveNum], cps);
4901       /* End of additions by Tord */
4902     }
4903
4904     /* [HGM] setting up the opening has brought engine in force mode! */
4905     /*       Send 'go' if we are in a mode where machine should play. */
4906     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4907         (gameMode == TwoMachinesPlay   ||
4908 #if ZIPPY
4909          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4910 #endif
4911          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4912         SendToProgram("go\n", cps);
4913   if (appData.debugMode) {
4914     fprintf(debugFP, "(extra)\n");
4915   }
4916     }
4917     setboardSpoiledMachineBlack = 0;
4918 }
4919
4920 void
4921 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4922 {
4923     char user_move[MSG_SIZ];
4924     char suffix[4];
4925
4926     if(gameInfo.variant == VariantSChess && promoChar) {
4927         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4928         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4929     } else suffix[0] = NULLCHAR;
4930
4931     switch (moveType) {
4932       default:
4933         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4934                 (int)moveType, fromX, fromY, toX, toY);
4935         DisplayError(user_move + strlen("say "), 0);
4936         break;
4937       case WhiteKingSideCastle:
4938       case BlackKingSideCastle:
4939       case WhiteQueenSideCastleWild:
4940       case BlackQueenSideCastleWild:
4941       /* PUSH Fabien */
4942       case WhiteHSideCastleFR:
4943       case BlackHSideCastleFR:
4944       /* POP Fabien */
4945         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4946         break;
4947       case WhiteQueenSideCastle:
4948       case BlackQueenSideCastle:
4949       case WhiteKingSideCastleWild:
4950       case BlackKingSideCastleWild:
4951       /* PUSH Fabien */
4952       case WhiteASideCastleFR:
4953       case BlackASideCastleFR:
4954       /* POP Fabien */
4955         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
4956         break;
4957       case WhiteNonPromotion:
4958       case BlackNonPromotion:
4959         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4960         break;
4961       case WhitePromotion:
4962       case BlackPromotion:
4963         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4964           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4965                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4966                 PieceToChar(WhiteFerz));
4967         else if(gameInfo.variant == VariantGreat)
4968           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4969                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4970                 PieceToChar(WhiteMan));
4971         else
4972           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4973                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4974                 promoChar);
4975         break;
4976       case WhiteDrop:
4977       case BlackDrop:
4978       drop:
4979         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4980                  ToUpper(PieceToChar((ChessSquare) fromX)),
4981                  AAA + toX, ONE + toY);
4982         break;
4983       case IllegalMove:  /* could be a variant we don't quite understand */
4984         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4985       case NormalMove:
4986       case WhiteCapturesEnPassant:
4987       case BlackCapturesEnPassant:
4988         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4989                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4990         break;
4991     }
4992     SendToICS(user_move);
4993     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4994         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4995 }
4996
4997 void
4998 UploadGameEvent ()
4999 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5000     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5001     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5002     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5003       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5004       return;
5005     }
5006     if(gameMode != IcsExamining) { // is this ever not the case?
5007         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5008
5009         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5010           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5011         } else { // on FICS we must first go to general examine mode
5012           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5013         }
5014         if(gameInfo.variant != VariantNormal) {
5015             // try figure out wild number, as xboard names are not always valid on ICS
5016             for(i=1; i<=36; i++) {
5017               snprintf(buf, MSG_SIZ, "wild/%d", i);
5018                 if(StringToVariant(buf) == gameInfo.variant) break;
5019             }
5020             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5021             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5022             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5023         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5024         SendToICS(ics_prefix);
5025         SendToICS(buf);
5026         if(startedFromSetupPosition || backwardMostMove != 0) {
5027           fen = PositionToFEN(backwardMostMove, NULL);
5028           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5029             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5030             SendToICS(buf);
5031           } else { // FICS: everything has to set by separate bsetup commands
5032             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5033             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5034             SendToICS(buf);
5035             if(!WhiteOnMove(backwardMostMove)) {
5036                 SendToICS("bsetup tomove black\n");
5037             }
5038             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5039             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5040             SendToICS(buf);
5041             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5042             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5043             SendToICS(buf);
5044             i = boards[backwardMostMove][EP_STATUS];
5045             if(i >= 0) { // set e.p.
5046               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5047                 SendToICS(buf);
5048             }
5049             bsetup++;
5050           }
5051         }
5052       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5053             SendToICS("bsetup done\n"); // switch to normal examining.
5054     }
5055     for(i = backwardMostMove; i<last; i++) {
5056         char buf[20];
5057         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5058         SendToICS(buf);
5059     }
5060     SendToICS(ics_prefix);
5061     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5062 }
5063
5064 void
5065 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5066 {
5067     if (rf == DROP_RANK) {
5068       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5069       sprintf(move, "%c@%c%c\n",
5070                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5071     } else {
5072         if (promoChar == 'x' || promoChar == NULLCHAR) {
5073           sprintf(move, "%c%c%c%c\n",
5074                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5075         } else {
5076             sprintf(move, "%c%c%c%c%c\n",
5077                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5078         }
5079     }
5080 }
5081
5082 void
5083 ProcessICSInitScript (FILE *f)
5084 {
5085     char buf[MSG_SIZ];
5086
5087     while (fgets(buf, MSG_SIZ, f)) {
5088         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5089     }
5090
5091     fclose(f);
5092 }
5093
5094
5095 static int lastX, lastY, selectFlag, dragging;
5096
5097 void
5098 Sweep (int step)
5099 {
5100     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5101     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5102     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5103     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5104     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5105     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5106     do {
5107         promoSweep -= step;
5108         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5109         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5110         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5111         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5112         if(!step) step = -1;
5113     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5114             appData.testLegality && (promoSweep == king ||
5115             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5116     ChangeDragPiece(promoSweep);
5117 }
5118
5119 int
5120 PromoScroll (int x, int y)
5121 {
5122   int step = 0;
5123
5124   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5125   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5126   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5127   if(!step) return FALSE;
5128   lastX = x; lastY = y;
5129   if((promoSweep < BlackPawn) == flipView) step = -step;
5130   if(step > 0) selectFlag = 1;
5131   if(!selectFlag) Sweep(step);
5132   return FALSE;
5133 }
5134
5135 void
5136 NextPiece (int step)
5137 {
5138     ChessSquare piece = boards[currentMove][toY][toX];
5139     do {
5140         pieceSweep -= step;
5141         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5142         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5143         if(!step) step = -1;
5144     } while(PieceToChar(pieceSweep) == '.');
5145     boards[currentMove][toY][toX] = pieceSweep;
5146     DrawPosition(FALSE, boards[currentMove]);
5147     boards[currentMove][toY][toX] = piece;
5148 }
5149 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5150 void
5151 AlphaRank (char *move, int n)
5152 {
5153 //    char *p = move, c; int x, y;
5154
5155     if (appData.debugMode) {
5156         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5157     }
5158
5159     if(move[1]=='*' &&
5160        move[2]>='0' && move[2]<='9' &&
5161        move[3]>='a' && move[3]<='x'    ) {
5162         move[1] = '@';
5163         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5164         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5165     } else
5166     if(move[0]>='0' && move[0]<='9' &&
5167        move[1]>='a' && move[1]<='x' &&
5168        move[2]>='0' && move[2]<='9' &&
5169        move[3]>='a' && move[3]<='x'    ) {
5170         /* input move, Shogi -> normal */
5171         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5172         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5173         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5174         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5175     } else
5176     if(move[1]=='@' &&
5177        move[3]>='0' && move[3]<='9' &&
5178        move[2]>='a' && move[2]<='x'    ) {
5179         move[1] = '*';
5180         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5181         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5182     } else
5183     if(
5184        move[0]>='a' && move[0]<='x' &&
5185        move[3]>='0' && move[3]<='9' &&
5186        move[2]>='a' && move[2]<='x'    ) {
5187          /* output move, normal -> Shogi */
5188         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5189         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5190         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5191         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5192         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5193     }
5194     if (appData.debugMode) {
5195         fprintf(debugFP, "   out = '%s'\n", move);
5196     }
5197 }
5198
5199 char yy_textstr[8000];
5200
5201 /* Parser for moves from gnuchess, ICS, or user typein box */
5202 Boolean
5203 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5204 {
5205     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5206
5207     switch (*moveType) {
5208       case WhitePromotion:
5209       case BlackPromotion:
5210       case WhiteNonPromotion:
5211       case BlackNonPromotion:
5212       case NormalMove:
5213       case WhiteCapturesEnPassant:
5214       case BlackCapturesEnPassant:
5215       case WhiteKingSideCastle:
5216       case WhiteQueenSideCastle:
5217       case BlackKingSideCastle:
5218       case BlackQueenSideCastle:
5219       case WhiteKingSideCastleWild:
5220       case WhiteQueenSideCastleWild:
5221       case BlackKingSideCastleWild:
5222       case BlackQueenSideCastleWild:
5223       /* Code added by Tord: */
5224       case WhiteHSideCastleFR:
5225       case WhiteASideCastleFR:
5226       case BlackHSideCastleFR:
5227       case BlackASideCastleFR:
5228       /* End of code added by Tord */
5229       case IllegalMove:         /* bug or odd chess variant */
5230         *fromX = currentMoveString[0] - AAA;
5231         *fromY = currentMoveString[1] - ONE;
5232         *toX = currentMoveString[2] - AAA;
5233         *toY = currentMoveString[3] - ONE;
5234         *promoChar = currentMoveString[4];
5235         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5236             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5237     if (appData.debugMode) {
5238         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5239     }
5240             *fromX = *fromY = *toX = *toY = 0;
5241             return FALSE;
5242         }
5243         if (appData.testLegality) {
5244           return (*moveType != IllegalMove);
5245         } else {
5246           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5247                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5248         }
5249
5250       case WhiteDrop:
5251       case BlackDrop:
5252         *fromX = *moveType == WhiteDrop ?
5253           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5254           (int) CharToPiece(ToLower(currentMoveString[0]));
5255         *fromY = DROP_RANK;
5256         *toX = currentMoveString[2] - AAA;
5257         *toY = currentMoveString[3] - ONE;
5258         *promoChar = NULLCHAR;
5259         return TRUE;
5260
5261       case AmbiguousMove:
5262       case ImpossibleMove:
5263       case EndOfFile:
5264       case ElapsedTime:
5265       case Comment:
5266       case PGNTag:
5267       case NAG:
5268       case WhiteWins:
5269       case BlackWins:
5270       case GameIsDrawn:
5271       default:
5272     if (appData.debugMode) {
5273         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5274     }
5275         /* bug? */
5276         *fromX = *fromY = *toX = *toY = 0;
5277         *promoChar = NULLCHAR;
5278         return FALSE;
5279     }
5280 }
5281
5282 Boolean pushed = FALSE;
5283 char *lastParseAttempt;
5284
5285 void
5286 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5287 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5288   int fromX, fromY, toX, toY; char promoChar;
5289   ChessMove moveType;
5290   Boolean valid;
5291   int nr = 0;
5292
5293   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5294     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5295     pushed = TRUE;
5296   }
5297   endPV = forwardMostMove;
5298   do {
5299     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5300     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5301     lastParseAttempt = pv;
5302     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5303 if(appData.debugMode){
5304 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);
5305 }
5306     if(!valid && nr == 0 &&
5307        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5308         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5309         // Hande case where played move is different from leading PV move
5310         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5311         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5312         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5313         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5314           endPV += 2; // if position different, keep this
5315           moveList[endPV-1][0] = fromX + AAA;
5316           moveList[endPV-1][1] = fromY + ONE;
5317           moveList[endPV-1][2] = toX + AAA;
5318           moveList[endPV-1][3] = toY + ONE;
5319           parseList[endPV-1][0] = NULLCHAR;
5320           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5321         }
5322       }
5323     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5324     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5325     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5326     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5327         valid++; // allow comments in PV
5328         continue;
5329     }
5330     nr++;
5331     if(endPV+1 > framePtr) break; // no space, truncate
5332     if(!valid) break;
5333     endPV++;
5334     CopyBoard(boards[endPV], boards[endPV-1]);
5335     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5336     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5337     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5338     CoordsToAlgebraic(boards[endPV - 1],
5339                              PosFlags(endPV - 1),
5340                              fromY, fromX, toY, toX, promoChar,
5341                              parseList[endPV - 1]);
5342   } while(valid);
5343   if(atEnd == 2) return; // used hidden, for PV conversion
5344   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5345   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5346   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5347                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5348   DrawPosition(TRUE, boards[currentMove]);
5349 }
5350
5351 int
5352 MultiPV (ChessProgramState *cps)
5353 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5354         int i;
5355         for(i=0; i<cps->nrOptions; i++)
5356             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5357                 return i;
5358         return -1;
5359 }
5360
5361 Boolean
5362 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5363 {
5364         int startPV, multi, lineStart, origIndex = index;
5365         char *p, buf2[MSG_SIZ];
5366
5367         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5368         lastX = x; lastY = y;
5369         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5370         lineStart = startPV = index;
5371         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5372         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5373         index = startPV;
5374         do{ while(buf[index] && buf[index] != '\n') index++;
5375         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5376         buf[index] = 0;
5377         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5378                 int n = first.option[multi].value;
5379                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5380                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5381                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5382                 first.option[multi].value = n;
5383                 *start = *end = 0;
5384                 return FALSE;
5385         }
5386         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5387         *start = startPV; *end = index-1;
5388         return TRUE;
5389 }
5390
5391 char *
5392 PvToSAN (char *pv)
5393 {
5394         static char buf[10*MSG_SIZ];
5395         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5396         *buf = NULLCHAR;
5397         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5398         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5399         for(i = forwardMostMove; i<endPV; i++){
5400             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5401             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5402             k += strlen(buf+k);
5403         }
5404         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5405         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5406         endPV = savedEnd;
5407         return buf;
5408 }
5409
5410 Boolean
5411 LoadPV (int x, int y)
5412 { // called on right mouse click to load PV
5413   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5414   lastX = x; lastY = y;
5415   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5416   return TRUE;
5417 }
5418
5419 void
5420 UnLoadPV ()
5421 {
5422   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5423   if(endPV < 0) return;
5424   endPV = -1;
5425   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5426         Boolean saveAnimate = appData.animate;
5427         if(pushed) {
5428             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5429                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5430             } else storedGames--; // abandon shelved tail of original game
5431         }
5432         pushed = FALSE;
5433         forwardMostMove = currentMove;
5434         currentMove = oldFMM;
5435         appData.animate = FALSE;
5436         ToNrEvent(forwardMostMove);
5437         appData.animate = saveAnimate;
5438   }
5439   currentMove = forwardMostMove;
5440   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5441   ClearPremoveHighlights();
5442   DrawPosition(TRUE, boards[currentMove]);
5443 }
5444
5445 void
5446 MovePV (int x, int y, int h)
5447 { // step through PV based on mouse coordinates (called on mouse move)
5448   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5449
5450   // we must somehow check if right button is still down (might be released off board!)
5451   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5452   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5453   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5454   if(!step) return;
5455   lastX = x; lastY = y;
5456
5457   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5458   if(endPV < 0) return;
5459   if(y < margin) step = 1; else
5460   if(y > h - margin) step = -1;
5461   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5462   currentMove += step;
5463   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5464   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5465                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5466   DrawPosition(FALSE, boards[currentMove]);
5467 }
5468
5469
5470 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5471 // All positions will have equal probability, but the current method will not provide a unique
5472 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5473 #define DARK 1
5474 #define LITE 2
5475 #define ANY 3
5476
5477 int squaresLeft[4];
5478 int piecesLeft[(int)BlackPawn];
5479 int seed, nrOfShuffles;
5480
5481 void
5482 GetPositionNumber ()
5483 {       // sets global variable seed
5484         int i;
5485
5486         seed = appData.defaultFrcPosition;
5487         if(seed < 0) { // randomize based on time for negative FRC position numbers
5488                 for(i=0; i<50; i++) seed += random();
5489                 seed = random() ^ random() >> 8 ^ random() << 8;
5490                 if(seed<0) seed = -seed;
5491         }
5492 }
5493
5494 int
5495 put (Board board, int pieceType, int rank, int n, int shade)
5496 // put the piece on the (n-1)-th empty squares of the given shade
5497 {
5498         int i;
5499
5500         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5501                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5502                         board[rank][i] = (ChessSquare) pieceType;
5503                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5504                         squaresLeft[ANY]--;
5505                         piecesLeft[pieceType]--;
5506                         return i;
5507                 }
5508         }
5509         return -1;
5510 }
5511
5512
5513 void
5514 AddOnePiece (Board board, int pieceType, int rank, int shade)
5515 // calculate where the next piece goes, (any empty square), and put it there
5516 {
5517         int i;
5518
5519         i = seed % squaresLeft[shade];
5520         nrOfShuffles *= squaresLeft[shade];
5521         seed /= squaresLeft[shade];
5522         put(board, pieceType, rank, i, shade);
5523 }
5524
5525 void
5526 AddTwoPieces (Board board, int pieceType, int rank)
5527 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5528 {
5529         int i, n=squaresLeft[ANY], j=n-1, k;
5530
5531         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5532         i = seed % k;  // pick one
5533         nrOfShuffles *= k;
5534         seed /= k;
5535         while(i >= j) i -= j--;
5536         j = n - 1 - j; i += j;
5537         put(board, pieceType, rank, j, ANY);
5538         put(board, pieceType, rank, i, ANY);
5539 }
5540
5541 void
5542 SetUpShuffle (Board board, int number)
5543 {
5544         int i, p, first=1;
5545
5546         GetPositionNumber(); nrOfShuffles = 1;
5547
5548         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5549         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5550         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5551
5552         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5553
5554         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5555             p = (int) board[0][i];
5556             if(p < (int) BlackPawn) piecesLeft[p] ++;
5557             board[0][i] = EmptySquare;
5558         }
5559
5560         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5561             // shuffles restricted to allow normal castling put KRR first
5562             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5563                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5564             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5565                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5566             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5567                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5568             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5569                 put(board, WhiteRook, 0, 0, ANY);
5570             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5571         }
5572
5573         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5574             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5575             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5576                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5577                 while(piecesLeft[p] >= 2) {
5578                     AddOnePiece(board, p, 0, LITE);
5579                     AddOnePiece(board, p, 0, DARK);
5580                 }
5581                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5582             }
5583
5584         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5585             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5586             // but we leave King and Rooks for last, to possibly obey FRC restriction
5587             if(p == (int)WhiteRook) continue;
5588             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5589             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5590         }
5591
5592         // now everything is placed, except perhaps King (Unicorn) and Rooks
5593
5594         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5595             // Last King gets castling rights
5596             while(piecesLeft[(int)WhiteUnicorn]) {
5597                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5598                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5599             }
5600
5601             while(piecesLeft[(int)WhiteKing]) {
5602                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5603                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5604             }
5605
5606
5607         } else {
5608             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5609             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5610         }
5611
5612         // Only Rooks can be left; simply place them all
5613         while(piecesLeft[(int)WhiteRook]) {
5614                 i = put(board, WhiteRook, 0, 0, ANY);
5615                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5616                         if(first) {
5617                                 first=0;
5618                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5619                         }
5620                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5621                 }
5622         }
5623         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5624             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5625         }
5626
5627         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5628 }
5629
5630 int
5631 SetCharTable (char *table, const char * map)
5632 /* [HGM] moved here from winboard.c because of its general usefulness */
5633 /*       Basically a safe strcpy that uses the last character as King */
5634 {
5635     int result = FALSE; int NrPieces;
5636
5637     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5638                     && NrPieces >= 12 && !(NrPieces&1)) {
5639         int i; /* [HGM] Accept even length from 12 to 34 */
5640
5641         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5642         for( i=0; i<NrPieces/2-1; i++ ) {
5643             table[i] = map[i];
5644             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5645         }
5646         table[(int) WhiteKing]  = map[NrPieces/2-1];
5647         table[(int) BlackKing]  = map[NrPieces-1];
5648
5649         result = TRUE;
5650     }
5651
5652     return result;
5653 }
5654
5655 void
5656 Prelude (Board board)
5657 {       // [HGM] superchess: random selection of exo-pieces
5658         int i, j, k; ChessSquare p;
5659         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5660
5661         GetPositionNumber(); // use FRC position number
5662
5663         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5664             SetCharTable(pieceToChar, appData.pieceToCharTable);
5665             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5666                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5667         }
5668
5669         j = seed%4;                 seed /= 4;
5670         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5671         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5672         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5673         j = seed%3 + (seed%3 >= j); seed /= 3;
5674         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5675         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5676         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5677         j = seed%3;                 seed /= 3;
5678         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5679         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5680         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5681         j = seed%2 + (seed%2 >= j); seed /= 2;
5682         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5683         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5684         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5685         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5686         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5687         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5688         put(board, exoPieces[0],    0, 0, ANY);
5689         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5690 }
5691
5692 void
5693 InitPosition (int redraw)
5694 {
5695     ChessSquare (* pieces)[BOARD_FILES];
5696     int i, j, pawnRow, overrule,
5697     oldx = gameInfo.boardWidth,
5698     oldy = gameInfo.boardHeight,
5699     oldh = gameInfo.holdingsWidth;
5700     static int oldv;
5701
5702     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5703
5704     /* [AS] Initialize pv info list [HGM] and game status */
5705     {
5706         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5707             pvInfoList[i].depth = 0;
5708             boards[i][EP_STATUS] = EP_NONE;
5709             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5710         }
5711
5712         initialRulePlies = 0; /* 50-move counter start */
5713
5714         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5715         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5716     }
5717
5718
5719     /* [HGM] logic here is completely changed. In stead of full positions */
5720     /* the initialized data only consist of the two backranks. The switch */
5721     /* selects which one we will use, which is than copied to the Board   */
5722     /* initialPosition, which for the rest is initialized by Pawns and    */
5723     /* empty squares. This initial position is then copied to boards[0],  */
5724     /* possibly after shuffling, so that it remains available.            */
5725
5726     gameInfo.holdingsWidth = 0; /* default board sizes */
5727     gameInfo.boardWidth    = 8;
5728     gameInfo.boardHeight   = 8;
5729     gameInfo.holdingsSize  = 0;
5730     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5731     for(i=0; i<BOARD_FILES-2; i++)
5732       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5733     initialPosition[EP_STATUS] = EP_NONE;
5734     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5735     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5736          SetCharTable(pieceNickName, appData.pieceNickNames);
5737     else SetCharTable(pieceNickName, "............");
5738     pieces = FIDEArray;
5739
5740     switch (gameInfo.variant) {
5741     case VariantFischeRandom:
5742       shuffleOpenings = TRUE;
5743     default:
5744       break;
5745     case VariantShatranj:
5746       pieces = ShatranjArray;
5747       nrCastlingRights = 0;
5748       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5749       break;
5750     case VariantMakruk:
5751       pieces = makrukArray;
5752       nrCastlingRights = 0;
5753       startedFromSetupPosition = TRUE;
5754       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5755       break;
5756     case VariantTwoKings:
5757       pieces = twoKingsArray;
5758       break;
5759     case VariantGrand:
5760       pieces = GrandArray;
5761       nrCastlingRights = 0;
5762       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5763       gameInfo.boardWidth = 10;
5764       gameInfo.boardHeight = 10;
5765       gameInfo.holdingsSize = 7;
5766       break;
5767     case VariantCapaRandom:
5768       shuffleOpenings = TRUE;
5769     case VariantCapablanca:
5770       pieces = CapablancaArray;
5771       gameInfo.boardWidth = 10;
5772       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5773       break;
5774     case VariantGothic:
5775       pieces = GothicArray;
5776       gameInfo.boardWidth = 10;
5777       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5778       break;
5779     case VariantSChess:
5780       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5781       gameInfo.holdingsSize = 7;
5782       break;
5783     case VariantJanus:
5784       pieces = JanusArray;
5785       gameInfo.boardWidth = 10;
5786       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5787       nrCastlingRights = 6;
5788         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5789         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5790         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5791         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5792         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5793         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5794       break;
5795     case VariantFalcon:
5796       pieces = FalconArray;
5797       gameInfo.boardWidth = 10;
5798       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5799       break;
5800     case VariantXiangqi:
5801       pieces = XiangqiArray;
5802       gameInfo.boardWidth  = 9;
5803       gameInfo.boardHeight = 10;
5804       nrCastlingRights = 0;
5805       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5806       break;
5807     case VariantShogi:
5808       pieces = ShogiArray;
5809       gameInfo.boardWidth  = 9;
5810       gameInfo.boardHeight = 9;
5811       gameInfo.holdingsSize = 7;
5812       nrCastlingRights = 0;
5813       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5814       break;
5815     case VariantCourier:
5816       pieces = CourierArray;
5817       gameInfo.boardWidth  = 12;
5818       nrCastlingRights = 0;
5819       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5820       break;
5821     case VariantKnightmate:
5822       pieces = KnightmateArray;
5823       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5824       break;
5825     case VariantSpartan:
5826       pieces = SpartanArray;
5827       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5828       break;
5829     case VariantFairy:
5830       pieces = fairyArray;
5831       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5832       break;
5833     case VariantGreat:
5834       pieces = GreatArray;
5835       gameInfo.boardWidth = 10;
5836       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5837       gameInfo.holdingsSize = 8;
5838       break;
5839     case VariantSuper:
5840       pieces = FIDEArray;
5841       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5842       gameInfo.holdingsSize = 8;
5843       startedFromSetupPosition = TRUE;
5844       break;
5845     case VariantCrazyhouse:
5846     case VariantBughouse:
5847       pieces = FIDEArray;
5848       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5849       gameInfo.holdingsSize = 5;
5850       break;
5851     case VariantWildCastle:
5852       pieces = FIDEArray;
5853       /* !!?shuffle with kings guaranteed to be on d or e file */
5854       shuffleOpenings = 1;
5855       break;
5856     case VariantNoCastle:
5857       pieces = FIDEArray;
5858       nrCastlingRights = 0;
5859       /* !!?unconstrained back-rank shuffle */
5860       shuffleOpenings = 1;
5861       break;
5862     }
5863
5864     overrule = 0;
5865     if(appData.NrFiles >= 0) {
5866         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5867         gameInfo.boardWidth = appData.NrFiles;
5868     }
5869     if(appData.NrRanks >= 0) {
5870         gameInfo.boardHeight = appData.NrRanks;
5871     }
5872     if(appData.holdingsSize >= 0) {
5873         i = appData.holdingsSize;
5874         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5875         gameInfo.holdingsSize = i;
5876     }
5877     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5878     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5879         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5880
5881     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5882     if(pawnRow < 1) pawnRow = 1;
5883     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5884
5885     /* User pieceToChar list overrules defaults */
5886     if(appData.pieceToCharTable != NULL)
5887         SetCharTable(pieceToChar, appData.pieceToCharTable);
5888
5889     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5890
5891         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5892             s = (ChessSquare) 0; /* account holding counts in guard band */
5893         for( i=0; i<BOARD_HEIGHT; i++ )
5894             initialPosition[i][j] = s;
5895
5896         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5897         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5898         initialPosition[pawnRow][j] = WhitePawn;
5899         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5900         if(gameInfo.variant == VariantXiangqi) {
5901             if(j&1) {
5902                 initialPosition[pawnRow][j] =
5903                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5904                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5905                    initialPosition[2][j] = WhiteCannon;
5906                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5907                 }
5908             }
5909         }
5910         if(gameInfo.variant == VariantGrand) {
5911             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5912                initialPosition[0][j] = WhiteRook;
5913                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5914             }
5915         }
5916         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5917     }
5918     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5919
5920             j=BOARD_LEFT+1;
5921             initialPosition[1][j] = WhiteBishop;
5922             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5923             j=BOARD_RGHT-2;
5924             initialPosition[1][j] = WhiteRook;
5925             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5926     }
5927
5928     if( nrCastlingRights == -1) {
5929         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5930         /*       This sets default castling rights from none to normal corners   */
5931         /* Variants with other castling rights must set them themselves above    */
5932         nrCastlingRights = 6;
5933
5934         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5935         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5936         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5937         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5938         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5939         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5940      }
5941
5942      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5943      if(gameInfo.variant == VariantGreat) { // promotion commoners
5944         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5945         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5946         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5947         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5948      }
5949      if( gameInfo.variant == VariantSChess ) {
5950       initialPosition[1][0] = BlackMarshall;
5951       initialPosition[2][0] = BlackAngel;
5952       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5953       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5954       initialPosition[1][1] = initialPosition[2][1] = 
5955       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5956      }
5957   if (appData.debugMode) {
5958     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5959   }
5960     if(shuffleOpenings) {
5961         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5962         startedFromSetupPosition = TRUE;
5963     }
5964     if(startedFromPositionFile) {
5965       /* [HGM] loadPos: use PositionFile for every new game */
5966       CopyBoard(initialPosition, filePosition);
5967       for(i=0; i<nrCastlingRights; i++)
5968           initialRights[i] = filePosition[CASTLING][i];
5969       startedFromSetupPosition = TRUE;
5970     }
5971
5972     CopyBoard(boards[0], initialPosition);
5973
5974     if(oldx != gameInfo.boardWidth ||
5975        oldy != gameInfo.boardHeight ||
5976        oldv != gameInfo.variant ||
5977        oldh != gameInfo.holdingsWidth
5978                                          )
5979             InitDrawingSizes(-2 ,0);
5980
5981     oldv = gameInfo.variant;
5982     if (redraw)
5983       DrawPosition(TRUE, boards[currentMove]);
5984 }
5985
5986 void
5987 SendBoard (ChessProgramState *cps, int moveNum)
5988 {
5989     char message[MSG_SIZ];
5990
5991     if (cps->useSetboard) {
5992       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5993       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5994       SendToProgram(message, cps);
5995       free(fen);
5996
5997     } else {
5998       ChessSquare *bp;
5999       int i, j, left=0, right=BOARD_WIDTH;
6000       /* Kludge to set black to move, avoiding the troublesome and now
6001        * deprecated "black" command.
6002        */
6003       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6004         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6005
6006       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6007
6008       SendToProgram("edit\n", cps);
6009       SendToProgram("#\n", cps);
6010       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6011         bp = &boards[moveNum][i][left];
6012         for (j = left; j < right; j++, bp++) {
6013           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6014           if ((int) *bp < (int) BlackPawn) {
6015             if(j == BOARD_RGHT+1)
6016                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6017             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6018             if(message[0] == '+' || message[0] == '~') {
6019               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6020                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6021                         AAA + j, ONE + i);
6022             }
6023             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6024                 message[1] = BOARD_RGHT   - 1 - j + '1';
6025                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6026             }
6027             SendToProgram(message, cps);
6028           }
6029         }
6030       }
6031
6032       SendToProgram("c\n", cps);
6033       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6034         bp = &boards[moveNum][i][left];
6035         for (j = left; j < right; j++, bp++) {
6036           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6037           if (((int) *bp != (int) EmptySquare)
6038               && ((int) *bp >= (int) BlackPawn)) {
6039             if(j == BOARD_LEFT-2)
6040                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6041             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6042                     AAA + j, ONE + i);
6043             if(message[0] == '+' || message[0] == '~') {
6044               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6045                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6046                         AAA + j, ONE + i);
6047             }
6048             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6049                 message[1] = BOARD_RGHT   - 1 - j + '1';
6050                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6051             }
6052             SendToProgram(message, cps);
6053           }
6054         }
6055       }
6056
6057       SendToProgram(".\n", cps);
6058     }
6059     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6060 }
6061
6062 ChessSquare
6063 DefaultPromoChoice (int white)
6064 {
6065     ChessSquare result;
6066     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6067         result = WhiteFerz; // no choice
6068     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6069         result= WhiteKing; // in Suicide Q is the last thing we want
6070     else if(gameInfo.variant == VariantSpartan)
6071         result = white ? WhiteQueen : WhiteAngel;
6072     else result = WhiteQueen;
6073     if(!white) result = WHITE_TO_BLACK result;
6074     return result;
6075 }
6076
6077 static int autoQueen; // [HGM] oneclick
6078
6079 int
6080 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6081 {
6082     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6083     /* [HGM] add Shogi promotions */
6084     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6085     ChessSquare piece;
6086     ChessMove moveType;
6087     Boolean premove;
6088
6089     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6090     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6091
6092     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6093       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6094         return FALSE;
6095
6096     piece = boards[currentMove][fromY][fromX];
6097     if(gameInfo.variant == VariantShogi) {
6098         promotionZoneSize = BOARD_HEIGHT/3;
6099         highestPromotingPiece = (int)WhiteFerz;
6100     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6101         promotionZoneSize = 3;
6102     }
6103
6104     // Treat Lance as Pawn when it is not representing Amazon
6105     if(gameInfo.variant != VariantSuper) {
6106         if(piece == WhiteLance) piece = WhitePawn; else
6107         if(piece == BlackLance) piece = BlackPawn;
6108     }
6109
6110     // next weed out all moves that do not touch the promotion zone at all
6111     if((int)piece >= BlackPawn) {
6112         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6113              return FALSE;
6114         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6115     } else {
6116         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6117            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6118     }
6119
6120     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6121
6122     // weed out mandatory Shogi promotions
6123     if(gameInfo.variant == VariantShogi) {
6124         if(piece >= BlackPawn) {
6125             if(toY == 0 && piece == BlackPawn ||
6126                toY == 0 && piece == BlackQueen ||
6127                toY <= 1 && piece == BlackKnight) {
6128                 *promoChoice = '+';
6129                 return FALSE;
6130             }
6131         } else {
6132             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6133                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6134                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6135                 *promoChoice = '+';
6136                 return FALSE;
6137             }
6138         }
6139     }
6140
6141     // weed out obviously illegal Pawn moves
6142     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6143         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6144         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6145         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6146         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6147         // note we are not allowed to test for valid (non-)capture, due to premove
6148     }
6149
6150     // we either have a choice what to promote to, or (in Shogi) whether to promote
6151     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6152         *promoChoice = PieceToChar(BlackFerz);  // no choice
6153         return FALSE;
6154     }
6155     // no sense asking what we must promote to if it is going to explode...
6156     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6157         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6158         return FALSE;
6159     }
6160     // give caller the default choice even if we will not make it
6161     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6162     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6163     if(        sweepSelect && gameInfo.variant != VariantGreat
6164                            && gameInfo.variant != VariantGrand
6165                            && gameInfo.variant != VariantSuper) return FALSE;
6166     if(autoQueen) return FALSE; // predetermined
6167
6168     // suppress promotion popup on illegal moves that are not premoves
6169     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6170               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6171     if(appData.testLegality && !premove) {
6172         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6173                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6174         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6175             return FALSE;
6176     }
6177
6178     return TRUE;
6179 }
6180
6181 int
6182 InPalace (int row, int column)
6183 {   /* [HGM] for Xiangqi */
6184     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6185          column < (BOARD_WIDTH + 4)/2 &&
6186          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6187     return FALSE;
6188 }
6189
6190 int
6191 PieceForSquare (int x, int y)
6192 {
6193   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6194      return -1;
6195   else
6196      return boards[currentMove][y][x];
6197 }
6198
6199 int
6200 OKToStartUserMove (int x, int y)
6201 {
6202     ChessSquare from_piece;
6203     int white_piece;
6204
6205     if (matchMode) return FALSE;
6206     if (gameMode == EditPosition) return TRUE;
6207
6208     if (x >= 0 && y >= 0)
6209       from_piece = boards[currentMove][y][x];
6210     else
6211       from_piece = EmptySquare;
6212
6213     if (from_piece == EmptySquare) return FALSE;
6214
6215     white_piece = (int)from_piece >= (int)WhitePawn &&
6216       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6217
6218     switch (gameMode) {
6219       case AnalyzeFile:
6220       case TwoMachinesPlay:
6221       case EndOfGame:
6222         return FALSE;
6223
6224       case IcsObserving:
6225       case IcsIdle:
6226         return FALSE;
6227
6228       case MachinePlaysWhite:
6229       case IcsPlayingBlack:
6230         if (appData.zippyPlay) return FALSE;
6231         if (white_piece) {
6232             DisplayMoveError(_("You are playing Black"));
6233             return FALSE;
6234         }
6235         break;
6236
6237       case MachinePlaysBlack:
6238       case IcsPlayingWhite:
6239         if (appData.zippyPlay) return FALSE;
6240         if (!white_piece) {
6241             DisplayMoveError(_("You are playing White"));
6242             return FALSE;
6243         }
6244         break;
6245
6246       case PlayFromGameFile:
6247             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6248       case EditGame:
6249         if (!white_piece && WhiteOnMove(currentMove)) {
6250             DisplayMoveError(_("It is White's turn"));
6251             return FALSE;
6252         }
6253         if (white_piece && !WhiteOnMove(currentMove)) {
6254             DisplayMoveError(_("It is Black's turn"));
6255             return FALSE;
6256         }
6257         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6258             /* Editing correspondence game history */
6259             /* Could disallow this or prompt for confirmation */
6260             cmailOldMove = -1;
6261         }
6262         break;
6263
6264       case BeginningOfGame:
6265         if (appData.icsActive) return FALSE;
6266         if (!appData.noChessProgram) {
6267             if (!white_piece) {
6268                 DisplayMoveError(_("You are playing White"));
6269                 return FALSE;
6270             }
6271         }
6272         break;
6273
6274       case Training:
6275         if (!white_piece && WhiteOnMove(currentMove)) {
6276             DisplayMoveError(_("It is White's turn"));
6277             return FALSE;
6278         }
6279         if (white_piece && !WhiteOnMove(currentMove)) {
6280             DisplayMoveError(_("It is Black's turn"));
6281             return FALSE;
6282         }
6283         break;
6284
6285       default:
6286       case IcsExamining:
6287         break;
6288     }
6289     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6290         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6291         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6292         && gameMode != AnalyzeFile && gameMode != Training) {
6293         DisplayMoveError(_("Displayed position is not current"));
6294         return FALSE;
6295     }
6296     return TRUE;
6297 }
6298
6299 Boolean
6300 OnlyMove (int *x, int *y, Boolean captures) 
6301 {
6302     DisambiguateClosure cl;
6303     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6304     switch(gameMode) {
6305       case MachinePlaysBlack:
6306       case IcsPlayingWhite:
6307       case BeginningOfGame:
6308         if(!WhiteOnMove(currentMove)) return FALSE;
6309         break;
6310       case MachinePlaysWhite:
6311       case IcsPlayingBlack:
6312         if(WhiteOnMove(currentMove)) return FALSE;
6313         break;
6314       case EditGame:
6315         break;
6316       default:
6317         return FALSE;
6318     }
6319     cl.pieceIn = EmptySquare;
6320     cl.rfIn = *y;
6321     cl.ffIn = *x;
6322     cl.rtIn = -1;
6323     cl.ftIn = -1;
6324     cl.promoCharIn = NULLCHAR;
6325     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6326     if( cl.kind == NormalMove ||
6327         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6328         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6329         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6330       fromX = cl.ff;
6331       fromY = cl.rf;
6332       *x = cl.ft;
6333       *y = cl.rt;
6334       return TRUE;
6335     }
6336     if(cl.kind != ImpossibleMove) return FALSE;
6337     cl.pieceIn = EmptySquare;
6338     cl.rfIn = -1;
6339     cl.ffIn = -1;
6340     cl.rtIn = *y;
6341     cl.ftIn = *x;
6342     cl.promoCharIn = NULLCHAR;
6343     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6344     if( cl.kind == NormalMove ||
6345         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6346         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6347         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6348       fromX = cl.ff;
6349       fromY = cl.rf;
6350       *x = cl.ft;
6351       *y = cl.rt;
6352       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6353       return TRUE;
6354     }
6355     return FALSE;
6356 }
6357
6358 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6359 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6360 int lastLoadGameUseList = FALSE;
6361 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6362 ChessMove lastLoadGameStart = EndOfFile;
6363
6364 void
6365 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6366 {
6367     ChessMove moveType;
6368     ChessSquare pdown, pup;
6369
6370     /* Check if the user is playing in turn.  This is complicated because we
6371        let the user "pick up" a piece before it is his turn.  So the piece he
6372        tried to pick up may have been captured by the time he puts it down!
6373        Therefore we use the color the user is supposed to be playing in this
6374        test, not the color of the piece that is currently on the starting
6375        square---except in EditGame mode, where the user is playing both
6376        sides; fortunately there the capture race can't happen.  (It can
6377        now happen in IcsExamining mode, but that's just too bad.  The user
6378        will get a somewhat confusing message in that case.)
6379        */
6380
6381     switch (gameMode) {
6382       case AnalyzeFile:
6383       case TwoMachinesPlay:
6384       case EndOfGame:
6385       case IcsObserving:
6386       case IcsIdle:
6387         /* We switched into a game mode where moves are not accepted,
6388            perhaps while the mouse button was down. */
6389         return;
6390
6391       case MachinePlaysWhite:
6392         /* User is moving for Black */
6393         if (WhiteOnMove(currentMove)) {
6394             DisplayMoveError(_("It is White's turn"));
6395             return;
6396         }
6397         break;
6398
6399       case MachinePlaysBlack:
6400         /* User is moving for White */
6401         if (!WhiteOnMove(currentMove)) {
6402             DisplayMoveError(_("It is Black's turn"));
6403             return;
6404         }
6405         break;
6406
6407       case PlayFromGameFile:
6408             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6409       case EditGame:
6410       case IcsExamining:
6411       case BeginningOfGame:
6412       case AnalyzeMode:
6413       case Training:
6414         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6415         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6416             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6417             /* User is moving for Black */
6418             if (WhiteOnMove(currentMove)) {
6419                 DisplayMoveError(_("It is White's turn"));
6420                 return;
6421             }
6422         } else {
6423             /* User is moving for White */
6424             if (!WhiteOnMove(currentMove)) {
6425                 DisplayMoveError(_("It is Black's turn"));
6426                 return;
6427             }
6428         }
6429         break;
6430
6431       case IcsPlayingBlack:
6432         /* User is moving for Black */
6433         if (WhiteOnMove(currentMove)) {
6434             if (!appData.premove) {
6435                 DisplayMoveError(_("It is White's turn"));
6436             } else if (toX >= 0 && toY >= 0) {
6437                 premoveToX = toX;
6438                 premoveToY = toY;
6439                 premoveFromX = fromX;
6440                 premoveFromY = fromY;
6441                 premovePromoChar = promoChar;
6442                 gotPremove = 1;
6443                 if (appData.debugMode)
6444                     fprintf(debugFP, "Got premove: fromX %d,"
6445                             "fromY %d, toX %d, toY %d\n",
6446                             fromX, fromY, toX, toY);
6447             }
6448             return;
6449         }
6450         break;
6451
6452       case IcsPlayingWhite:
6453         /* User is moving for White */
6454         if (!WhiteOnMove(currentMove)) {
6455             if (!appData.premove) {
6456                 DisplayMoveError(_("It is Black's turn"));
6457             } else if (toX >= 0 && toY >= 0) {
6458                 premoveToX = toX;
6459                 premoveToY = toY;
6460                 premoveFromX = fromX;
6461                 premoveFromY = fromY;
6462                 premovePromoChar = promoChar;
6463                 gotPremove = 1;
6464                 if (appData.debugMode)
6465                     fprintf(debugFP, "Got premove: fromX %d,"
6466                             "fromY %d, toX %d, toY %d\n",
6467                             fromX, fromY, toX, toY);
6468             }
6469             return;
6470         }
6471         break;
6472
6473       default:
6474         break;
6475
6476       case EditPosition:
6477         /* EditPosition, empty square, or different color piece;
6478            click-click move is possible */
6479         if (toX == -2 || toY == -2) {
6480             boards[0][fromY][fromX] = EmptySquare;
6481             DrawPosition(FALSE, boards[currentMove]);
6482             return;
6483         } else if (toX >= 0 && toY >= 0) {
6484             boards[0][toY][toX] = boards[0][fromY][fromX];
6485             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6486                 if(boards[0][fromY][0] != EmptySquare) {
6487                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6488                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6489                 }
6490             } else
6491             if(fromX == BOARD_RGHT+1) {
6492                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6493                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6494                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6495                 }
6496             } else
6497             boards[0][fromY][fromX] = EmptySquare;
6498             DrawPosition(FALSE, boards[currentMove]);
6499             return;
6500         }
6501         return;
6502     }
6503
6504     if(toX < 0 || toY < 0) return;
6505     pdown = boards[currentMove][fromY][fromX];
6506     pup = boards[currentMove][toY][toX];
6507
6508     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6509     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6510          if( pup != EmptySquare ) return;
6511          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6512            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6513                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6514            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6515            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6516            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6517            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6518          fromY = DROP_RANK;
6519     }
6520
6521     /* [HGM] always test for legality, to get promotion info */
6522     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6523                                          fromY, fromX, toY, toX, promoChar);
6524
6525     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6526
6527     /* [HGM] but possibly ignore an IllegalMove result */
6528     if (appData.testLegality) {
6529         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6530             DisplayMoveError(_("Illegal move"));
6531             return;
6532         }
6533     }
6534
6535     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6536 }
6537
6538 /* Common tail of UserMoveEvent and DropMenuEvent */
6539 int
6540 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6541 {
6542     char *bookHit = 0;
6543
6544     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6545         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6546         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6547         if(WhiteOnMove(currentMove)) {
6548             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6549         } else {
6550             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6551         }
6552     }
6553
6554     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6555        move type in caller when we know the move is a legal promotion */
6556     if(moveType == NormalMove && promoChar)
6557         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6558
6559     /* [HGM] <popupFix> The following if has been moved here from
6560        UserMoveEvent(). Because it seemed to belong here (why not allow
6561        piece drops in training games?), and because it can only be
6562        performed after it is known to what we promote. */
6563     if (gameMode == Training) {
6564       /* compare the move played on the board to the next move in the
6565        * game. If they match, display the move and the opponent's response.
6566        * If they don't match, display an error message.
6567        */
6568       int saveAnimate;
6569       Board testBoard;
6570       CopyBoard(testBoard, boards[currentMove]);
6571       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6572
6573       if (CompareBoards(testBoard, boards[currentMove+1])) {
6574         ForwardInner(currentMove+1);
6575
6576         /* Autoplay the opponent's response.
6577          * if appData.animate was TRUE when Training mode was entered,
6578          * the response will be animated.
6579          */
6580         saveAnimate = appData.animate;
6581         appData.animate = animateTraining;
6582         ForwardInner(currentMove+1);
6583         appData.animate = saveAnimate;
6584
6585         /* check for the end of the game */
6586         if (currentMove >= forwardMostMove) {
6587           gameMode = PlayFromGameFile;
6588           ModeHighlight();
6589           SetTrainingModeOff();
6590           DisplayInformation(_("End of game"));
6591         }
6592       } else {
6593         DisplayError(_("Incorrect move"), 0);
6594       }
6595       return 1;
6596     }
6597
6598   /* Ok, now we know that the move is good, so we can kill
6599      the previous line in Analysis Mode */
6600   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6601                                 && currentMove < forwardMostMove) {
6602     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6603     else forwardMostMove = currentMove;
6604   }
6605
6606   /* If we need the chess program but it's dead, restart it */
6607   ResurrectChessProgram();
6608
6609   /* A user move restarts a paused game*/
6610   if (pausing)
6611     PauseEvent();
6612
6613   thinkOutput[0] = NULLCHAR;
6614
6615   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6616
6617   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6618     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6619     return 1;
6620   }
6621
6622   if (gameMode == BeginningOfGame) {
6623     if (appData.noChessProgram) {
6624       gameMode = EditGame;
6625       SetGameInfo();
6626     } else {
6627       char buf[MSG_SIZ];
6628       gameMode = MachinePlaysBlack;
6629       StartClocks();
6630       SetGameInfo();
6631       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6632       DisplayTitle(buf);
6633       if (first.sendName) {
6634         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6635         SendToProgram(buf, &first);
6636       }
6637       StartClocks();
6638     }
6639     ModeHighlight();
6640   }
6641
6642   /* Relay move to ICS or chess engine */
6643   if (appData.icsActive) {
6644     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6645         gameMode == IcsExamining) {
6646       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6647         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6648         SendToICS("draw ");
6649         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6650       }
6651       // also send plain move, in case ICS does not understand atomic claims
6652       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6653       ics_user_moved = 1;
6654     }
6655   } else {
6656     if (first.sendTime && (gameMode == BeginningOfGame ||
6657                            gameMode == MachinePlaysWhite ||
6658                            gameMode == MachinePlaysBlack)) {
6659       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6660     }
6661     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6662          // [HGM] book: if program might be playing, let it use book
6663         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6664         first.maybeThinking = TRUE;
6665     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6666         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6667         SendBoard(&first, currentMove+1);
6668     } else SendMoveToProgram(forwardMostMove-1, &first);
6669     if (currentMove == cmailOldMove + 1) {
6670       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6671     }
6672   }
6673
6674   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6675
6676   switch (gameMode) {
6677   case EditGame:
6678     if(appData.testLegality)
6679     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6680     case MT_NONE:
6681     case MT_CHECK:
6682       break;
6683     case MT_CHECKMATE:
6684     case MT_STAINMATE:
6685       if (WhiteOnMove(currentMove)) {
6686         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6687       } else {
6688         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6689       }
6690       break;
6691     case MT_STALEMATE:
6692       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6693       break;
6694     }
6695     break;
6696
6697   case MachinePlaysBlack:
6698   case MachinePlaysWhite:
6699     /* disable certain menu options while machine is thinking */
6700     SetMachineThinkingEnables();
6701     break;
6702
6703   default:
6704     break;
6705   }
6706
6707   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6708   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6709
6710   if(bookHit) { // [HGM] book: simulate book reply
6711         static char bookMove[MSG_SIZ]; // a bit generous?
6712
6713         programStats.nodes = programStats.depth = programStats.time =
6714         programStats.score = programStats.got_only_move = 0;
6715         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6716
6717         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6718         strcat(bookMove, bookHit);
6719         HandleMachineMove(bookMove, &first);
6720   }
6721   return 1;
6722 }
6723
6724 void
6725 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6726 {
6727     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6728     Markers *m = (Markers *) closure;
6729     if(rf == fromY && ff == fromX)
6730         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6731                          || kind == WhiteCapturesEnPassant
6732                          || kind == BlackCapturesEnPassant);
6733     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6734 }
6735
6736 void
6737 MarkTargetSquares (int clear)
6738 {
6739   int x, y;
6740   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6741      !appData.testLegality || gameMode == EditPosition) return;
6742   if(clear) {
6743     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6744   } else {
6745     int capt = 0;
6746     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6747     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6748       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6749       if(capt)
6750       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6751     }
6752   }
6753   DrawPosition(TRUE, NULL);
6754 }
6755
6756 int
6757 Explode (Board board, int fromX, int fromY, int toX, int toY)
6758 {
6759     if(gameInfo.variant == VariantAtomic &&
6760        (board[toY][toX] != EmptySquare ||                     // capture?
6761         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6762                          board[fromY][fromX] == BlackPawn   )
6763       )) {
6764         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6765         return TRUE;
6766     }
6767     return FALSE;
6768 }
6769
6770 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6771
6772 int
6773 CanPromote (ChessSquare piece, int y)
6774 {
6775         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6776         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6777         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6778            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6779            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6780                                                   gameInfo.variant == VariantMakruk) return FALSE;
6781         return (piece == BlackPawn && y == 1 ||
6782                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6783                 piece == BlackLance && y == 1 ||
6784                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6785 }
6786
6787 void
6788 LeftClick (ClickType clickType, int xPix, int yPix)
6789 {
6790     int x, y;
6791     Boolean saveAnimate;
6792     static int second = 0, promotionChoice = 0, clearFlag = 0;
6793     char promoChoice = NULLCHAR;
6794     ChessSquare piece;
6795
6796     if(appData.seekGraph && appData.icsActive && loggedOn &&
6797         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6798         SeekGraphClick(clickType, xPix, yPix, 0);
6799         return;
6800     }
6801
6802     if (clickType == Press) ErrorPopDown();
6803
6804     x = EventToSquare(xPix, BOARD_WIDTH);
6805     y = EventToSquare(yPix, BOARD_HEIGHT);
6806     if (!flipView && y >= 0) {
6807         y = BOARD_HEIGHT - 1 - y;
6808     }
6809     if (flipView && x >= 0) {
6810         x = BOARD_WIDTH - 1 - x;
6811     }
6812
6813     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6814         defaultPromoChoice = promoSweep;
6815         promoSweep = EmptySquare;   // terminate sweep
6816         promoDefaultAltered = TRUE;
6817         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6818     }
6819
6820     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6821         if(clickType == Release) return; // ignore upclick of click-click destination
6822         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6823         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6824         if(gameInfo.holdingsWidth &&
6825                 (WhiteOnMove(currentMove)
6826                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6827                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6828             // click in right holdings, for determining promotion piece
6829             ChessSquare p = boards[currentMove][y][x];
6830             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6831             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6832             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6833                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6834                 fromX = fromY = -1;
6835                 return;
6836             }
6837         }
6838         DrawPosition(FALSE, boards[currentMove]);
6839         return;
6840     }
6841
6842     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6843     if(clickType == Press
6844             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6845               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6846               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6847         return;
6848
6849     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6850         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6851
6852     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6853         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6854                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6855         defaultPromoChoice = DefaultPromoChoice(side);
6856     }
6857
6858     autoQueen = appData.alwaysPromoteToQueen;
6859
6860     if (fromX == -1) {
6861       int originalY = y;
6862       gatingPiece = EmptySquare;
6863       if (clickType != Press) {
6864         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6865             DragPieceEnd(xPix, yPix); dragging = 0;
6866             DrawPosition(FALSE, NULL);
6867         }
6868         return;
6869       }
6870       fromX = x; fromY = y; toX = toY = -1;
6871       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6872          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6873          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6874             /* First square */
6875             if (OKToStartUserMove(fromX, fromY)) {
6876                 second = 0;
6877                 MarkTargetSquares(0);
6878                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6879                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6880                     promoSweep = defaultPromoChoice;
6881                     selectFlag = 0; lastX = xPix; lastY = yPix;
6882                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6883                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6884                 }
6885                 if (appData.highlightDragging) {
6886                     SetHighlights(fromX, fromY, -1, -1);
6887                 }
6888             } else fromX = fromY = -1;
6889             return;
6890         }
6891     }
6892
6893     /* fromX != -1 */
6894     if (clickType == Press && gameMode != EditPosition) {
6895         ChessSquare fromP;
6896         ChessSquare toP;
6897         int frc;
6898
6899         // ignore off-board to clicks
6900         if(y < 0 || x < 0) return;
6901
6902         /* Check if clicking again on the same color piece */
6903         fromP = boards[currentMove][fromY][fromX];
6904         toP = boards[currentMove][y][x];
6905         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6906         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6907              WhitePawn <= toP && toP <= WhiteKing &&
6908              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6909              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6910             (BlackPawn <= fromP && fromP <= BlackKing &&
6911              BlackPawn <= toP && toP <= BlackKing &&
6912              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6913              !(fromP == BlackKing && toP == BlackRook && frc))) {
6914             /* Clicked again on same color piece -- changed his mind */
6915             second = (x == fromX && y == fromY);
6916             promoDefaultAltered = FALSE;
6917             MarkTargetSquares(1);
6918            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6919             if (appData.highlightDragging) {
6920                 SetHighlights(x, y, -1, -1);
6921             } else {
6922                 ClearHighlights();
6923             }
6924             if (OKToStartUserMove(x, y)) {
6925                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6926                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6927                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6928                  gatingPiece = boards[currentMove][fromY][fromX];
6929                 else gatingPiece = EmptySquare;
6930                 fromX = x;
6931                 fromY = y; dragging = 1;
6932                 MarkTargetSquares(0);
6933                 DragPieceBegin(xPix, yPix, FALSE);
6934                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6935                     promoSweep = defaultPromoChoice;
6936                     selectFlag = 0; lastX = xPix; lastY = yPix;
6937                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6938                 }
6939             }
6940            }
6941            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6942            second = FALSE; 
6943         }
6944         // ignore clicks on holdings
6945         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6946     }
6947
6948     if (clickType == Release && x == fromX && y == fromY) {
6949         DragPieceEnd(xPix, yPix); dragging = 0;
6950         if(clearFlag) {
6951             // a deferred attempt to click-click move an empty square on top of a piece
6952             boards[currentMove][y][x] = EmptySquare;
6953             ClearHighlights();
6954             DrawPosition(FALSE, boards[currentMove]);
6955             fromX = fromY = -1; clearFlag = 0;
6956             return;
6957         }
6958         if (appData.animateDragging) {
6959             /* Undo animation damage if any */
6960             DrawPosition(FALSE, NULL);
6961         }
6962         if (second) {
6963             /* Second up/down in same square; just abort move */
6964             second = 0;
6965             fromX = fromY = -1;
6966             gatingPiece = EmptySquare;
6967             ClearHighlights();
6968             gotPremove = 0;
6969             ClearPremoveHighlights();
6970         } else {
6971             /* First upclick in same square; start click-click mode */
6972             SetHighlights(x, y, -1, -1);
6973         }
6974         return;
6975     }
6976
6977     clearFlag = 0;
6978
6979     /* we now have a different from- and (possibly off-board) to-square */
6980     /* Completed move */
6981     toX = x;
6982     toY = y;
6983     saveAnimate = appData.animate;
6984     MarkTargetSquares(1);
6985     if (clickType == Press) {
6986         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6987             // must be Edit Position mode with empty-square selected
6988             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
6989             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6990             return;
6991         }
6992         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
6993             ChessSquare piece = boards[currentMove][fromY][fromX];
6994             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
6995             promoSweep = defaultPromoChoice;
6996             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
6997             selectFlag = 0; lastX = xPix; lastY = yPix;
6998             Sweep(0); // Pawn that is going to promote: preview promotion piece
6999             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7000             DrawPosition(FALSE, boards[currentMove]);
7001             return;
7002         }
7003         /* Finish clickclick move */
7004         if (appData.animate || appData.highlightLastMove) {
7005             SetHighlights(fromX, fromY, toX, toY);
7006         } else {
7007             ClearHighlights();
7008         }
7009     } else {
7010         /* Finish drag move */
7011         if (appData.highlightLastMove) {
7012             SetHighlights(fromX, fromY, toX, toY);
7013         } else {
7014             ClearHighlights();
7015         }
7016         DragPieceEnd(xPix, yPix); dragging = 0;
7017         /* Don't animate move and drag both */
7018         appData.animate = FALSE;
7019     }
7020
7021     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7022     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7023         ChessSquare piece = boards[currentMove][fromY][fromX];
7024         if(gameMode == EditPosition && piece != EmptySquare &&
7025            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7026             int n;
7027
7028             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7029                 n = PieceToNumber(piece - (int)BlackPawn);
7030                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7031                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7032                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7033             } else
7034             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7035                 n = PieceToNumber(piece);
7036                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7037                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7038                 boards[currentMove][n][BOARD_WIDTH-2]++;
7039             }
7040             boards[currentMove][fromY][fromX] = EmptySquare;
7041         }
7042         ClearHighlights();
7043         fromX = fromY = -1;
7044         DrawPosition(TRUE, boards[currentMove]);
7045         return;
7046     }
7047
7048     // off-board moves should not be highlighted
7049     if(x < 0 || y < 0) ClearHighlights();
7050
7051     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7052
7053     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7054         SetHighlights(fromX, fromY, toX, toY);
7055         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7056             // [HGM] super: promotion to captured piece selected from holdings
7057             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7058             promotionChoice = TRUE;
7059             // kludge follows to temporarily execute move on display, without promoting yet
7060             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7061             boards[currentMove][toY][toX] = p;
7062             DrawPosition(FALSE, boards[currentMove]);
7063             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7064             boards[currentMove][toY][toX] = q;
7065             DisplayMessage("Click in holdings to choose piece", "");
7066             return;
7067         }
7068         PromotionPopUp();
7069     } else {
7070         int oldMove = currentMove;
7071         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7072         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7073         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7074         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7075            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7076             DrawPosition(TRUE, boards[currentMove]);
7077         fromX = fromY = -1;
7078     }
7079     appData.animate = saveAnimate;
7080     if (appData.animate || appData.animateDragging) {
7081         /* Undo animation damage if needed */
7082         DrawPosition(FALSE, NULL);
7083     }
7084 }
7085
7086 int
7087 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7088 {   // front-end-free part taken out of PieceMenuPopup
7089     int whichMenu; int xSqr, ySqr;
7090
7091     if(seekGraphUp) { // [HGM] seekgraph
7092         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7093         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7094         return -2;
7095     }
7096
7097     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7098          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7099         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7100         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7101         if(action == Press)   {
7102             originalFlip = flipView;
7103             flipView = !flipView; // temporarily flip board to see game from partners perspective
7104             DrawPosition(TRUE, partnerBoard);
7105             DisplayMessage(partnerStatus, "");
7106             partnerUp = TRUE;
7107         } else if(action == Release) {
7108             flipView = originalFlip;
7109             DrawPosition(TRUE, boards[currentMove]);
7110             partnerUp = FALSE;
7111         }
7112         return -2;
7113     }
7114
7115     xSqr = EventToSquare(x, BOARD_WIDTH);
7116     ySqr = EventToSquare(y, BOARD_HEIGHT);
7117     if (action == Release) {
7118         if(pieceSweep != EmptySquare) {
7119             EditPositionMenuEvent(pieceSweep, toX, toY);
7120             pieceSweep = EmptySquare;
7121         } else UnLoadPV(); // [HGM] pv
7122     }
7123     if (action != Press) return -2; // return code to be ignored
7124     switch (gameMode) {
7125       case IcsExamining:
7126         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7127       case EditPosition:
7128         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7129         if (xSqr < 0 || ySqr < 0) return -1;
7130         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7131         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7132         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7133         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7134         NextPiece(0);
7135         return 2; // grab
7136       case IcsObserving:
7137         if(!appData.icsEngineAnalyze) return -1;
7138       case IcsPlayingWhite:
7139       case IcsPlayingBlack:
7140         if(!appData.zippyPlay) goto noZip;
7141       case AnalyzeMode:
7142       case AnalyzeFile:
7143       case MachinePlaysWhite:
7144       case MachinePlaysBlack:
7145       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7146         if (!appData.dropMenu) {
7147           LoadPV(x, y);
7148           return 2; // flag front-end to grab mouse events
7149         }
7150         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7151            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7152       case EditGame:
7153       noZip:
7154         if (xSqr < 0 || ySqr < 0) return -1;
7155         if (!appData.dropMenu || appData.testLegality &&
7156             gameInfo.variant != VariantBughouse &&
7157             gameInfo.variant != VariantCrazyhouse) return -1;
7158         whichMenu = 1; // drop menu
7159         break;
7160       default:
7161         return -1;
7162     }
7163
7164     if (((*fromX = xSqr) < 0) ||
7165         ((*fromY = ySqr) < 0)) {
7166         *fromX = *fromY = -1;
7167         return -1;
7168     }
7169     if (flipView)
7170       *fromX = BOARD_WIDTH - 1 - *fromX;
7171     else
7172       *fromY = BOARD_HEIGHT - 1 - *fromY;
7173
7174     return whichMenu;
7175 }
7176
7177 void
7178 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7179 {
7180 //    char * hint = lastHint;
7181     FrontEndProgramStats stats;
7182
7183     stats.which = cps == &first ? 0 : 1;
7184     stats.depth = cpstats->depth;
7185     stats.nodes = cpstats->nodes;
7186     stats.score = cpstats->score;
7187     stats.time = cpstats->time;
7188     stats.pv = cpstats->movelist;
7189     stats.hint = lastHint;
7190     stats.an_move_index = 0;
7191     stats.an_move_count = 0;
7192
7193     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7194         stats.hint = cpstats->move_name;
7195         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7196         stats.an_move_count = cpstats->nr_moves;
7197     }
7198
7199     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
7200
7201     SetProgramStats( &stats );
7202 }
7203
7204 void
7205 ClearEngineOutputPane (int which)
7206 {
7207     static FrontEndProgramStats dummyStats;
7208     dummyStats.which = which;
7209     dummyStats.pv = "#";
7210     SetProgramStats( &dummyStats );
7211 }
7212
7213 #define MAXPLAYERS 500
7214
7215 char *
7216 TourneyStandings (int display)
7217 {
7218     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7219     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7220     char result, *p, *names[MAXPLAYERS];
7221
7222     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7223         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7224     names[0] = p = strdup(appData.participants);
7225     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7226
7227     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7228
7229     while(result = appData.results[nr]) {
7230         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7231         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7232         wScore = bScore = 0;
7233         switch(result) {
7234           case '+': wScore = 2; break;
7235           case '-': bScore = 2; break;
7236           case '=': wScore = bScore = 1; break;
7237           case ' ':
7238           case '*': return strdup("busy"); // tourney not finished
7239         }
7240         score[w] += wScore;
7241         score[b] += bScore;
7242         games[w]++;
7243         games[b]++;
7244         nr++;
7245     }
7246     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7247     for(w=0; w<nPlayers; w++) {
7248         bScore = -1;
7249         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7250         ranking[w] = b; points[w] = bScore; score[b] = -2;
7251     }
7252     p = malloc(nPlayers*34+1);
7253     for(w=0; w<nPlayers && w<display; w++)
7254         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7255     free(names[0]);
7256     return p;
7257 }
7258
7259 void
7260 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7261 {       // count all piece types
7262         int p, f, r;
7263         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7264         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7265         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7266                 p = board[r][f];
7267                 pCnt[p]++;
7268                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7269                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7270                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7271                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7272                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7273                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7274         }
7275 }
7276
7277 int
7278 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7279 {
7280         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7281         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7282
7283         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7284         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7285         if(myPawns == 2 && nMine == 3) // KPP
7286             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7287         if(myPawns == 1 && nMine == 2) // KP
7288             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7289         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7290             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7291         if(myPawns) return FALSE;
7292         if(pCnt[WhiteRook+side])
7293             return pCnt[BlackRook-side] ||
7294                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7295                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7296                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7297         if(pCnt[WhiteCannon+side]) {
7298             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7299             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7300         }
7301         if(pCnt[WhiteKnight+side])
7302             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7303         return FALSE;
7304 }
7305
7306 int
7307 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7308 {
7309         VariantClass v = gameInfo.variant;
7310
7311         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7312         if(v == VariantShatranj) return TRUE; // always winnable through baring
7313         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7314         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7315
7316         if(v == VariantXiangqi) {
7317                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7318
7319                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7320                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7321                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7322                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7323                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7324                 if(stale) // we have at least one last-rank P plus perhaps C
7325                     return majors // KPKX
7326                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7327                 else // KCA*E*
7328                     return pCnt[WhiteFerz+side] // KCAK
7329                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7330                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7331                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7332
7333         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7334                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7335
7336                 if(nMine == 1) return FALSE; // bare King
7337                 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
7338                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7339                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7340                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7341                 if(pCnt[WhiteKnight+side])
7342                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7343                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7344                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7345                 if(nBishops)
7346                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7347                 if(pCnt[WhiteAlfil+side])
7348                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7349                 if(pCnt[WhiteWazir+side])
7350                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7351         }
7352
7353         return TRUE;
7354 }
7355
7356 int
7357 CompareWithRights (Board b1, Board b2)
7358 {
7359     int rights = 0;
7360     if(!CompareBoards(b1, b2)) return FALSE;
7361     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7362     /* compare castling rights */
7363     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7364            rights++; /* King lost rights, while rook still had them */
7365     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7366         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7367            rights++; /* but at least one rook lost them */
7368     }
7369     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7370            rights++;
7371     if( b1[CASTLING][5] != NoRights ) {
7372         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7373            rights++;
7374     }
7375     return rights == 0;
7376 }
7377
7378 int
7379 Adjudicate (ChessProgramState *cps)
7380 {       // [HGM] some adjudications useful with buggy engines
7381         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7382         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7383         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7384         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7385         int k, count = 0; static int bare = 1;
7386         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7387         Boolean canAdjudicate = !appData.icsActive;
7388
7389         // most tests only when we understand the game, i.e. legality-checking on
7390             if( appData.testLegality )
7391             {   /* [HGM] Some more adjudications for obstinate engines */
7392                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7393                 static int moveCount = 6;
7394                 ChessMove result;
7395                 char *reason = NULL;
7396
7397                 /* Count what is on board. */
7398                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7399
7400                 /* Some material-based adjudications that have to be made before stalemate test */
7401                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7402                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7403                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7404                      if(canAdjudicate && appData.checkMates) {
7405                          if(engineOpponent)
7406                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7407                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7408                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7409                          return 1;
7410                      }
7411                 }
7412
7413                 /* Bare King in Shatranj (loses) or Losers (wins) */
7414                 if( nrW == 1 || nrB == 1) {
7415                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7416                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7417                      if(canAdjudicate && appData.checkMates) {
7418                          if(engineOpponent)
7419                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7420                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7421                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7422                          return 1;
7423                      }
7424                   } else
7425                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7426                   {    /* bare King */
7427                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7428                         if(canAdjudicate && appData.checkMates) {
7429                             /* but only adjudicate if adjudication enabled */
7430                             if(engineOpponent)
7431                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7432                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7433                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7434                             return 1;
7435                         }
7436                   }
7437                 } else bare = 1;
7438
7439
7440             // don't wait for engine to announce game end if we can judge ourselves
7441             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7442               case MT_CHECK:
7443                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7444                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7445                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7446                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7447                             checkCnt++;
7448                         if(checkCnt >= 2) {
7449                             reason = "Xboard adjudication: 3rd check";
7450                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7451                             break;
7452                         }
7453                     }
7454                 }
7455               case MT_NONE:
7456               default:
7457                 break;
7458               case MT_STALEMATE:
7459               case MT_STAINMATE:
7460                 reason = "Xboard adjudication: Stalemate";
7461                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7462                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7463                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7464                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7465                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7466                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7467                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7468                                                                         EP_CHECKMATE : EP_WINS);
7469                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7470                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7471                 }
7472                 break;
7473               case MT_CHECKMATE:
7474                 reason = "Xboard adjudication: Checkmate";
7475                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7476                 break;
7477             }
7478
7479                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7480                     case EP_STALEMATE:
7481                         result = GameIsDrawn; break;
7482                     case EP_CHECKMATE:
7483                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7484                     case EP_WINS:
7485                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7486                     default:
7487                         result = EndOfFile;
7488                 }
7489                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7490                     if(engineOpponent)
7491                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7492                     GameEnds( result, reason, GE_XBOARD );
7493                     return 1;
7494                 }
7495
7496                 /* Next absolutely insufficient mating material. */
7497                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7498                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7499                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7500
7501                      /* always flag draws, for judging claims */
7502                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7503
7504                      if(canAdjudicate && appData.materialDraws) {
7505                          /* but only adjudicate them if adjudication enabled */
7506                          if(engineOpponent) {
7507                            SendToProgram("force\n", engineOpponent); // suppress reply
7508                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7509                          }
7510                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7511                          return 1;
7512                      }
7513                 }
7514
7515                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7516                 if(gameInfo.variant == VariantXiangqi ?
7517                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7518                  : nrW + nrB == 4 &&
7519                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7520                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7521                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7522                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7523                    ) ) {
7524                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7525                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7526                           if(engineOpponent) {
7527                             SendToProgram("force\n", engineOpponent); // suppress reply
7528                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7529                           }
7530                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7531                           return 1;
7532                      }
7533                 } else moveCount = 6;
7534             }
7535         if (appData.debugMode) { int i;
7536             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7537                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7538                     appData.drawRepeats);
7539             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7540               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7541
7542         }
7543
7544         // Repetition draws and 50-move rule can be applied independently of legality testing
7545
7546                 /* Check for rep-draws */
7547                 count = 0;
7548                 for(k = forwardMostMove-2;
7549                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7550                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7551                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7552                     k-=2)
7553                 {   int rights=0;
7554                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7555                         /* compare castling rights */
7556                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7557                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7558                                 rights++; /* King lost rights, while rook still had them */
7559                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7560                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7561                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7562                                    rights++; /* but at least one rook lost them */
7563                         }
7564                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7565                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7566                                 rights++;
7567                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7568                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7569                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7570                                    rights++;
7571                         }
7572                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7573                             && appData.drawRepeats > 1) {
7574                              /* adjudicate after user-specified nr of repeats */
7575                              int result = GameIsDrawn;
7576                              char *details = "XBoard adjudication: repetition draw";
7577                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7578                                 // [HGM] xiangqi: check for forbidden perpetuals
7579                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7580                                 for(m=forwardMostMove; m>k; m-=2) {
7581                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7582                                         ourPerpetual = 0; // the current mover did not always check
7583                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7584                                         hisPerpetual = 0; // the opponent did not always check
7585                                 }
7586                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7587                                                                         ourPerpetual, hisPerpetual);
7588                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7589                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7590                                     details = "Xboard adjudication: perpetual checking";
7591                                 } else
7592                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7593                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7594                                 } else
7595                                 // Now check for perpetual chases
7596                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7597                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7598                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7599                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7600                                         static char resdet[MSG_SIZ];
7601                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7602                                         details = resdet;
7603                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7604                                     } else
7605                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7606                                         break; // Abort repetition-checking loop.
7607                                 }
7608                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7609                              }
7610                              if(engineOpponent) {
7611                                SendToProgram("force\n", engineOpponent); // suppress reply
7612                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7613                              }
7614                              GameEnds( result, details, GE_XBOARD );
7615                              return 1;
7616                         }
7617                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7618                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7619                     }
7620                 }
7621
7622                 /* Now we test for 50-move draws. Determine ply count */
7623                 count = forwardMostMove;
7624                 /* look for last irreversble move */
7625                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7626                     count--;
7627                 /* if we hit starting position, add initial plies */
7628                 if( count == backwardMostMove )
7629                     count -= initialRulePlies;
7630                 count = forwardMostMove - count;
7631                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7632                         // adjust reversible move counter for checks in Xiangqi
7633                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7634                         if(i < backwardMostMove) i = backwardMostMove;
7635                         while(i <= forwardMostMove) {
7636                                 lastCheck = inCheck; // check evasion does not count
7637                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7638                                 if(inCheck || lastCheck) count--; // check does not count
7639                                 i++;
7640                         }
7641                 }
7642                 if( count >= 100)
7643                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7644                          /* this is used to judge if draw claims are legal */
7645                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7646                          if(engineOpponent) {
7647                            SendToProgram("force\n", engineOpponent); // suppress reply
7648                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7649                          }
7650                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7651                          return 1;
7652                 }
7653
7654                 /* if draw offer is pending, treat it as a draw claim
7655                  * when draw condition present, to allow engines a way to
7656                  * claim draws before making their move to avoid a race
7657                  * condition occurring after their move
7658                  */
7659                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7660                          char *p = NULL;
7661                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7662                              p = "Draw claim: 50-move rule";
7663                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7664                              p = "Draw claim: 3-fold repetition";
7665                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7666                              p = "Draw claim: insufficient mating material";
7667                          if( p != NULL && canAdjudicate) {
7668                              if(engineOpponent) {
7669                                SendToProgram("force\n", engineOpponent); // suppress reply
7670                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7671                              }
7672                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7673                              return 1;
7674                          }
7675                 }
7676
7677                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7678                     if(engineOpponent) {
7679                       SendToProgram("force\n", engineOpponent); // suppress reply
7680                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7681                     }
7682                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7683                     return 1;
7684                 }
7685         return 0;
7686 }
7687
7688 char *
7689 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7690 {   // [HGM] book: this routine intercepts moves to simulate book replies
7691     char *bookHit = NULL;
7692
7693     //first determine if the incoming move brings opponent into his book
7694     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7695         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7696     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7697     if(bookHit != NULL && !cps->bookSuspend) {
7698         // make sure opponent is not going to reply after receiving move to book position
7699         SendToProgram("force\n", cps);
7700         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7701     }
7702     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7703     // now arrange restart after book miss
7704     if(bookHit) {
7705         // after a book hit we never send 'go', and the code after the call to this routine
7706         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7707         char buf[MSG_SIZ], *move = bookHit;
7708         if(cps->useSAN) {
7709             int fromX, fromY, toX, toY;
7710             char promoChar;
7711             ChessMove moveType;
7712             move = buf + 30;
7713             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7714                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7715                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7716                                     PosFlags(forwardMostMove),
7717                                     fromY, fromX, toY, toX, promoChar, move);
7718             } else {
7719                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7720                 bookHit = NULL;
7721             }
7722         }
7723         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7724         SendToProgram(buf, cps);
7725         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7726     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7727         SendToProgram("go\n", cps);
7728         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7729     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7730         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7731             SendToProgram("go\n", cps);
7732         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7733     }
7734     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7735 }
7736
7737 char *savedMessage;
7738 ChessProgramState *savedState;
7739 void
7740 DeferredBookMove (void)
7741 {
7742         if(savedState->lastPing != savedState->lastPong)
7743                     ScheduleDelayedEvent(DeferredBookMove, 10);
7744         else
7745         HandleMachineMove(savedMessage, savedState);
7746 }
7747
7748 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7749
7750 void
7751 HandleMachineMove (char *message, ChessProgramState *cps)
7752 {
7753     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7754     char realname[MSG_SIZ];
7755     int fromX, fromY, toX, toY;
7756     ChessMove moveType;
7757     char promoChar;
7758     char *p, *pv=buf1;
7759     int machineWhite;
7760     char *bookHit;
7761
7762     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7763         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7764         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7765             DisplayError(_("Invalid pairing from pairing engine"), 0);
7766             return;
7767         }
7768         pairingReceived = 1;
7769         NextMatchGame();
7770         return; // Skim the pairing messages here.
7771     }
7772
7773     cps->userError = 0;
7774
7775 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7776     /*
7777      * Kludge to ignore BEL characters
7778      */
7779     while (*message == '\007') message++;
7780
7781     /*
7782      * [HGM] engine debug message: ignore lines starting with '#' character
7783      */
7784     if(cps->debug && *message == '#') return;
7785
7786     /*
7787      * Look for book output
7788      */
7789     if (cps == &first && bookRequested) {
7790         if (message[0] == '\t' || message[0] == ' ') {
7791             /* Part of the book output is here; append it */
7792             strcat(bookOutput, message);
7793             strcat(bookOutput, "  \n");
7794             return;
7795         } else if (bookOutput[0] != NULLCHAR) {
7796             /* All of book output has arrived; display it */
7797             char *p = bookOutput;
7798             while (*p != NULLCHAR) {
7799                 if (*p == '\t') *p = ' ';
7800                 p++;
7801             }
7802             DisplayInformation(bookOutput);
7803             bookRequested = FALSE;
7804             /* Fall through to parse the current output */
7805         }
7806     }
7807
7808     /*
7809      * Look for machine move.
7810      */
7811     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7812         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7813     {
7814         /* This method is only useful on engines that support ping */
7815         if (cps->lastPing != cps->lastPong) {
7816           if (gameMode == BeginningOfGame) {
7817             /* Extra move from before last new; ignore */
7818             if (appData.debugMode) {
7819                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7820             }
7821           } else {
7822             if (appData.debugMode) {
7823                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7824                         cps->which, gameMode);
7825             }
7826
7827             SendToProgram("undo\n", cps);
7828           }
7829           return;
7830         }
7831
7832         switch (gameMode) {
7833           case BeginningOfGame:
7834             /* Extra move from before last reset; ignore */
7835             if (appData.debugMode) {
7836                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7837             }
7838             return;
7839
7840           case EndOfGame:
7841           case IcsIdle:
7842           default:
7843             /* Extra move after we tried to stop.  The mode test is
7844                not a reliable way of detecting this problem, but it's
7845                the best we can do on engines that don't support ping.
7846             */
7847             if (appData.debugMode) {
7848                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7849                         cps->which, gameMode);
7850             }
7851             SendToProgram("undo\n", cps);
7852             return;
7853
7854           case MachinePlaysWhite:
7855           case IcsPlayingWhite:
7856             machineWhite = TRUE;
7857             break;
7858
7859           case MachinePlaysBlack:
7860           case IcsPlayingBlack:
7861             machineWhite = FALSE;
7862             break;
7863
7864           case TwoMachinesPlay:
7865             machineWhite = (cps->twoMachinesColor[0] == 'w');
7866             break;
7867         }
7868         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7869             if (appData.debugMode) {
7870                 fprintf(debugFP,
7871                         "Ignoring move out of turn by %s, gameMode %d"
7872                         ", forwardMost %d\n",
7873                         cps->which, gameMode, forwardMostMove);
7874             }
7875             return;
7876         }
7877
7878     if (appData.debugMode) { int f = forwardMostMove;
7879         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7880                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7881                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7882     }
7883         if(cps->alphaRank) AlphaRank(machineMove, 4);
7884         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7885                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7886             /* Machine move could not be parsed; ignore it. */
7887           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7888                     machineMove, _(cps->which));
7889             DisplayError(buf1, 0);
7890             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7891                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7892             if (gameMode == TwoMachinesPlay) {
7893               GameEnds(machineWhite ? BlackWins : WhiteWins,
7894                        buf1, GE_XBOARD);
7895             }
7896             return;
7897         }
7898
7899         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7900         /* So we have to redo legality test with true e.p. status here,  */
7901         /* to make sure an illegal e.p. capture does not slip through,   */
7902         /* to cause a forfeit on a justified illegal-move complaint      */
7903         /* of the opponent.                                              */
7904         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7905            ChessMove moveType;
7906            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7907                              fromY, fromX, toY, toX, promoChar);
7908             if (appData.debugMode) {
7909                 int i;
7910                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7911                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7912                 fprintf(debugFP, "castling rights\n");
7913             }
7914             if(moveType == IllegalMove) {
7915               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7916                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7917                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7918                            buf1, GE_XBOARD);
7919                 return;
7920            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7921            /* [HGM] Kludge to handle engines that send FRC-style castling
7922               when they shouldn't (like TSCP-Gothic) */
7923            switch(moveType) {
7924              case WhiteASideCastleFR:
7925              case BlackASideCastleFR:
7926                toX+=2;
7927                currentMoveString[2]++;
7928                break;
7929              case WhiteHSideCastleFR:
7930              case BlackHSideCastleFR:
7931                toX--;
7932                currentMoveString[2]--;
7933                break;
7934              default: ; // nothing to do, but suppresses warning of pedantic compilers
7935            }
7936         }
7937         hintRequested = FALSE;
7938         lastHint[0] = NULLCHAR;
7939         bookRequested = FALSE;
7940         /* Program may be pondering now */
7941         cps->maybeThinking = TRUE;
7942         if (cps->sendTime == 2) cps->sendTime = 1;
7943         if (cps->offeredDraw) cps->offeredDraw--;
7944
7945         /* [AS] Save move info*/
7946         pvInfoList[ forwardMostMove ].score = programStats.score;
7947         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7948         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7949
7950         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7951
7952         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7953         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7954             int count = 0;
7955
7956             while( count < adjudicateLossPlies ) {
7957                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7958
7959                 if( count & 1 ) {
7960                     score = -score; /* Flip score for winning side */
7961                 }
7962
7963                 if( score > adjudicateLossThreshold ) {
7964                     break;
7965                 }
7966
7967                 count++;
7968             }
7969
7970             if( count >= adjudicateLossPlies ) {
7971                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7972
7973                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7974                     "Xboard adjudication",
7975                     GE_XBOARD );
7976
7977                 return;
7978             }
7979         }
7980
7981         if(Adjudicate(cps)) {
7982             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7983             return; // [HGM] adjudicate: for all automatic game ends
7984         }
7985
7986 #if ZIPPY
7987         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7988             first.initDone) {
7989           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7990                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7991                 SendToICS("draw ");
7992                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7993           }
7994           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7995           ics_user_moved = 1;
7996           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7997                 char buf[3*MSG_SIZ];
7998
7999                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8000                         programStats.score / 100.,
8001                         programStats.depth,
8002                         programStats.time / 100.,
8003                         (unsigned int)programStats.nodes,
8004                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8005                         programStats.movelist);
8006                 SendToICS(buf);
8007 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8008           }
8009         }
8010 #endif
8011
8012         /* [AS] Clear stats for next move */
8013         ClearProgramStats();
8014         thinkOutput[0] = NULLCHAR;
8015         hiddenThinkOutputState = 0;
8016
8017         bookHit = NULL;
8018         if (gameMode == TwoMachinesPlay) {
8019             /* [HGM] relaying draw offers moved to after reception of move */
8020             /* and interpreting offer as claim if it brings draw condition */
8021             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8022                 SendToProgram("draw\n", cps->other);
8023             }
8024             if (cps->other->sendTime) {
8025                 SendTimeRemaining(cps->other,
8026                                   cps->other->twoMachinesColor[0] == 'w');
8027             }
8028             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8029             if (firstMove && !bookHit) {
8030                 firstMove = FALSE;
8031                 if (cps->other->useColors) {
8032                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8033                 }
8034                 SendToProgram("go\n", cps->other);
8035             }
8036             cps->other->maybeThinking = TRUE;
8037         }
8038
8039         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8040
8041         if (!pausing && appData.ringBellAfterMoves) {
8042             RingBell();
8043         }
8044
8045         /*
8046          * Reenable menu items that were disabled while
8047          * machine was thinking
8048          */
8049         if (gameMode != TwoMachinesPlay)
8050             SetUserThinkingEnables();
8051
8052         // [HGM] book: after book hit opponent has received move and is now in force mode
8053         // force the book reply into it, and then fake that it outputted this move by jumping
8054         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8055         if(bookHit) {
8056                 static char bookMove[MSG_SIZ]; // a bit generous?
8057
8058                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8059                 strcat(bookMove, bookHit);
8060                 message = bookMove;
8061                 cps = cps->other;
8062                 programStats.nodes = programStats.depth = programStats.time =
8063                 programStats.score = programStats.got_only_move = 0;
8064                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8065
8066                 if(cps->lastPing != cps->lastPong) {
8067                     savedMessage = message; // args for deferred call
8068                     savedState = cps;
8069                     ScheduleDelayedEvent(DeferredBookMove, 10);
8070                     return;
8071                 }
8072                 goto FakeBookMove;
8073         }
8074
8075         return;
8076     }
8077
8078     /* Set special modes for chess engines.  Later something general
8079      *  could be added here; for now there is just one kludge feature,
8080      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8081      *  when "xboard" is given as an interactive command.
8082      */
8083     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8084         cps->useSigint = FALSE;
8085         cps->useSigterm = FALSE;
8086     }
8087     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8088       ParseFeatures(message+8, cps);
8089       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8090     }
8091
8092     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8093                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8094       int dummy, s=6; char buf[MSG_SIZ];
8095       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8096       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8097       if(startedFromSetupPosition) return;
8098       ParseFEN(boards[0], &dummy, message+s);
8099       DrawPosition(TRUE, boards[0]);
8100       startedFromSetupPosition = TRUE;
8101       return;
8102     }
8103     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8104      * want this, I was asked to put it in, and obliged.
8105      */
8106     if (!strncmp(message, "setboard ", 9)) {
8107         Board initial_position;
8108
8109         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8110
8111         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8112             DisplayError(_("Bad FEN received from engine"), 0);
8113             return ;
8114         } else {
8115            Reset(TRUE, FALSE);
8116            CopyBoard(boards[0], initial_position);
8117            initialRulePlies = FENrulePlies;
8118            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8119            else gameMode = MachinePlaysBlack;
8120            DrawPosition(FALSE, boards[currentMove]);
8121         }
8122         return;
8123     }
8124
8125     /*
8126      * Look for communication commands
8127      */
8128     if (!strncmp(message, "telluser ", 9)) {
8129         if(message[9] == '\\' && message[10] == '\\')
8130             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8131         PlayTellSound();
8132         DisplayNote(message + 9);
8133         return;
8134     }
8135     if (!strncmp(message, "tellusererror ", 14)) {
8136         cps->userError = 1;
8137         if(message[14] == '\\' && message[15] == '\\')
8138             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8139         PlayTellSound();
8140         DisplayError(message + 14, 0);
8141         return;
8142     }
8143     if (!strncmp(message, "tellopponent ", 13)) {
8144       if (appData.icsActive) {
8145         if (loggedOn) {
8146           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8147           SendToICS(buf1);
8148         }
8149       } else {
8150         DisplayNote(message + 13);
8151       }
8152       return;
8153     }
8154     if (!strncmp(message, "tellothers ", 11)) {
8155       if (appData.icsActive) {
8156         if (loggedOn) {
8157           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8158           SendToICS(buf1);
8159         }
8160       }
8161       return;
8162     }
8163     if (!strncmp(message, "tellall ", 8)) {
8164       if (appData.icsActive) {
8165         if (loggedOn) {
8166           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8167           SendToICS(buf1);
8168         }
8169       } else {
8170         DisplayNote(message + 8);
8171       }
8172       return;
8173     }
8174     if (strncmp(message, "warning", 7) == 0) {
8175         /* Undocumented feature, use tellusererror in new code */
8176         DisplayError(message, 0);
8177         return;
8178     }
8179     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8180         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8181         strcat(realname, " query");
8182         AskQuestion(realname, buf2, buf1, cps->pr);
8183         return;
8184     }
8185     /* Commands from the engine directly to ICS.  We don't allow these to be
8186      *  sent until we are logged on. Crafty kibitzes have been known to
8187      *  interfere with the login process.
8188      */
8189     if (loggedOn) {
8190         if (!strncmp(message, "tellics ", 8)) {
8191             SendToICS(message + 8);
8192             SendToICS("\n");
8193             return;
8194         }
8195         if (!strncmp(message, "tellicsnoalias ", 15)) {
8196             SendToICS(ics_prefix);
8197             SendToICS(message + 15);
8198             SendToICS("\n");
8199             return;
8200         }
8201         /* The following are for backward compatibility only */
8202         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8203             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8204             SendToICS(ics_prefix);
8205             SendToICS(message);
8206             SendToICS("\n");
8207             return;
8208         }
8209     }
8210     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8211         return;
8212     }
8213     /*
8214      * If the move is illegal, cancel it and redraw the board.
8215      * Also deal with other error cases.  Matching is rather loose
8216      * here to accommodate engines written before the spec.
8217      */
8218     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8219         strncmp(message, "Error", 5) == 0) {
8220         if (StrStr(message, "name") ||
8221             StrStr(message, "rating") || StrStr(message, "?") ||
8222             StrStr(message, "result") || StrStr(message, "board") ||
8223             StrStr(message, "bk") || StrStr(message, "computer") ||
8224             StrStr(message, "variant") || StrStr(message, "hint") ||
8225             StrStr(message, "random") || StrStr(message, "depth") ||
8226             StrStr(message, "accepted")) {
8227             return;
8228         }
8229         if (StrStr(message, "protover")) {
8230           /* Program is responding to input, so it's apparently done
8231              initializing, and this error message indicates it is
8232              protocol version 1.  So we don't need to wait any longer
8233              for it to initialize and send feature commands. */
8234           FeatureDone(cps, 1);
8235           cps->protocolVersion = 1;
8236           return;
8237         }
8238         cps->maybeThinking = FALSE;
8239
8240         if (StrStr(message, "draw")) {
8241             /* Program doesn't have "draw" command */
8242             cps->sendDrawOffers = 0;
8243             return;
8244         }
8245         if (cps->sendTime != 1 &&
8246             (StrStr(message, "time") || StrStr(message, "otim"))) {
8247           /* Program apparently doesn't have "time" or "otim" command */
8248           cps->sendTime = 0;
8249           return;
8250         }
8251         if (StrStr(message, "analyze")) {
8252             cps->analysisSupport = FALSE;
8253             cps->analyzing = FALSE;
8254 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8255             EditGameEvent(); // [HGM] try to preserve loaded game
8256             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8257             DisplayError(buf2, 0);
8258             return;
8259         }
8260         if (StrStr(message, "(no matching move)st")) {
8261           /* Special kludge for GNU Chess 4 only */
8262           cps->stKludge = TRUE;
8263           SendTimeControl(cps, movesPerSession, timeControl,
8264                           timeIncrement, appData.searchDepth,
8265                           searchTime);
8266           return;
8267         }
8268         if (StrStr(message, "(no matching move)sd")) {
8269           /* Special kludge for GNU Chess 4 only */
8270           cps->sdKludge = TRUE;
8271           SendTimeControl(cps, movesPerSession, timeControl,
8272                           timeIncrement, appData.searchDepth,
8273                           searchTime);
8274           return;
8275         }
8276         if (!StrStr(message, "llegal")) {
8277             return;
8278         }
8279         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8280             gameMode == IcsIdle) return;
8281         if (forwardMostMove <= backwardMostMove) return;
8282         if (pausing) PauseEvent();
8283       if(appData.forceIllegal) {
8284             // [HGM] illegal: machine refused move; force position after move into it
8285           SendToProgram("force\n", cps);
8286           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8287                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8288                 // when black is to move, while there might be nothing on a2 or black
8289                 // might already have the move. So send the board as if white has the move.
8290                 // But first we must change the stm of the engine, as it refused the last move
8291                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8292                 if(WhiteOnMove(forwardMostMove)) {
8293                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8294                     SendBoard(cps, forwardMostMove); // kludgeless board
8295                 } else {
8296                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8297                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8298                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8299                 }
8300           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8301             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8302                  gameMode == TwoMachinesPlay)
8303               SendToProgram("go\n", cps);
8304             return;
8305       } else
8306         if (gameMode == PlayFromGameFile) {
8307             /* Stop reading this game file */
8308             gameMode = EditGame;
8309             ModeHighlight();
8310         }
8311         /* [HGM] illegal-move claim should forfeit game when Xboard */
8312         /* only passes fully legal moves                            */
8313         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8314             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8315                                 "False illegal-move claim", GE_XBOARD );
8316             return; // do not take back move we tested as valid
8317         }
8318         currentMove = forwardMostMove-1;
8319         DisplayMove(currentMove-1); /* before DisplayMoveError */
8320         SwitchClocks(forwardMostMove-1); // [HGM] race
8321         DisplayBothClocks();
8322         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8323                 parseList[currentMove], _(cps->which));
8324         DisplayMoveError(buf1);
8325         DrawPosition(FALSE, boards[currentMove]);
8326
8327         SetUserThinkingEnables();
8328         return;
8329     }
8330     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8331         /* Program has a broken "time" command that
8332            outputs a string not ending in newline.
8333            Don't use it. */
8334         cps->sendTime = 0;
8335     }
8336
8337     /*
8338      * If chess program startup fails, exit with an error message.
8339      * Attempts to recover here are futile.
8340      */
8341     if ((StrStr(message, "unknown host") != NULL)
8342         || (StrStr(message, "No remote directory") != NULL)
8343         || (StrStr(message, "not found") != NULL)
8344         || (StrStr(message, "No such file") != NULL)
8345         || (StrStr(message, "can't alloc") != NULL)
8346         || (StrStr(message, "Permission denied") != NULL)) {
8347
8348         cps->maybeThinking = FALSE;
8349         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8350                 _(cps->which), cps->program, cps->host, message);
8351         RemoveInputSource(cps->isr);
8352         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8353             if(cps == &first) appData.noChessProgram = TRUE;
8354             DisplayError(buf1, 0);
8355         }
8356         return;
8357     }
8358
8359     /*
8360      * Look for hint output
8361      */
8362     if (sscanf(message, "Hint: %s", buf1) == 1) {
8363         if (cps == &first && hintRequested) {
8364             hintRequested = FALSE;
8365             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8366                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8367                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8368                                     PosFlags(forwardMostMove),
8369                                     fromY, fromX, toY, toX, promoChar, buf1);
8370                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8371                 DisplayInformation(buf2);
8372             } else {
8373                 /* Hint move could not be parsed!? */
8374               snprintf(buf2, sizeof(buf2),
8375                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8376                         buf1, _(cps->which));
8377                 DisplayError(buf2, 0);
8378             }
8379         } else {
8380           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8381         }
8382         return;
8383     }
8384
8385     /*
8386      * Ignore other messages if game is not in progress
8387      */
8388     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8389         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8390
8391     /*
8392      * look for win, lose, draw, or draw offer
8393      */
8394     if (strncmp(message, "1-0", 3) == 0) {
8395         char *p, *q, *r = "";
8396         p = strchr(message, '{');
8397         if (p) {
8398             q = strchr(p, '}');
8399             if (q) {
8400                 *q = NULLCHAR;
8401                 r = p + 1;
8402             }
8403         }
8404         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8405         return;
8406     } else if (strncmp(message, "0-1", 3) == 0) {
8407         char *p, *q, *r = "";
8408         p = strchr(message, '{');
8409         if (p) {
8410             q = strchr(p, '}');
8411             if (q) {
8412                 *q = NULLCHAR;
8413                 r = p + 1;
8414             }
8415         }
8416         /* Kludge for Arasan 4.1 bug */
8417         if (strcmp(r, "Black resigns") == 0) {
8418             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8419             return;
8420         }
8421         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8422         return;
8423     } else if (strncmp(message, "1/2", 3) == 0) {
8424         char *p, *q, *r = "";
8425         p = strchr(message, '{');
8426         if (p) {
8427             q = strchr(p, '}');
8428             if (q) {
8429                 *q = NULLCHAR;
8430                 r = p + 1;
8431             }
8432         }
8433
8434         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8435         return;
8436
8437     } else if (strncmp(message, "White resign", 12) == 0) {
8438         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8439         return;
8440     } else if (strncmp(message, "Black resign", 12) == 0) {
8441         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8442         return;
8443     } else if (strncmp(message, "White matches", 13) == 0 ||
8444                strncmp(message, "Black matches", 13) == 0   ) {
8445         /* [HGM] ignore GNUShogi noises */
8446         return;
8447     } else if (strncmp(message, "White", 5) == 0 &&
8448                message[5] != '(' &&
8449                StrStr(message, "Black") == NULL) {
8450         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8451         return;
8452     } else if (strncmp(message, "Black", 5) == 0 &&
8453                message[5] != '(') {
8454         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8455         return;
8456     } else if (strcmp(message, "resign") == 0 ||
8457                strcmp(message, "computer resigns") == 0) {
8458         switch (gameMode) {
8459           case MachinePlaysBlack:
8460           case IcsPlayingBlack:
8461             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8462             break;
8463           case MachinePlaysWhite:
8464           case IcsPlayingWhite:
8465             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8466             break;
8467           case TwoMachinesPlay:
8468             if (cps->twoMachinesColor[0] == 'w')
8469               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8470             else
8471               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8472             break;
8473           default:
8474             /* can't happen */
8475             break;
8476         }
8477         return;
8478     } else if (strncmp(message, "opponent mates", 14) == 0) {
8479         switch (gameMode) {
8480           case MachinePlaysBlack:
8481           case IcsPlayingBlack:
8482             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8483             break;
8484           case MachinePlaysWhite:
8485           case IcsPlayingWhite:
8486             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8487             break;
8488           case TwoMachinesPlay:
8489             if (cps->twoMachinesColor[0] == 'w')
8490               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8491             else
8492               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8493             break;
8494           default:
8495             /* can't happen */
8496             break;
8497         }
8498         return;
8499     } else if (strncmp(message, "computer mates", 14) == 0) {
8500         switch (gameMode) {
8501           case MachinePlaysBlack:
8502           case IcsPlayingBlack:
8503             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8504             break;
8505           case MachinePlaysWhite:
8506           case IcsPlayingWhite:
8507             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8508             break;
8509           case TwoMachinesPlay:
8510             if (cps->twoMachinesColor[0] == 'w')
8511               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8512             else
8513               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8514             break;
8515           default:
8516             /* can't happen */
8517             break;
8518         }
8519         return;
8520     } else if (strncmp(message, "checkmate", 9) == 0) {
8521         if (WhiteOnMove(forwardMostMove)) {
8522             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8523         } else {
8524             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8525         }
8526         return;
8527     } else if (strstr(message, "Draw") != NULL ||
8528                strstr(message, "game is a draw") != NULL) {
8529         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8530         return;
8531     } else if (strstr(message, "offer") != NULL &&
8532                strstr(message, "draw") != NULL) {
8533 #if ZIPPY
8534         if (appData.zippyPlay && first.initDone) {
8535             /* Relay offer to ICS */
8536             SendToICS(ics_prefix);
8537             SendToICS("draw\n");
8538         }
8539 #endif
8540         cps->offeredDraw = 2; /* valid until this engine moves twice */
8541         if (gameMode == TwoMachinesPlay) {
8542             if (cps->other->offeredDraw) {
8543                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8544             /* [HGM] in two-machine mode we delay relaying draw offer      */
8545             /* until after we also have move, to see if it is really claim */
8546             }
8547         } else if (gameMode == MachinePlaysWhite ||
8548                    gameMode == MachinePlaysBlack) {
8549           if (userOfferedDraw) {
8550             DisplayInformation(_("Machine accepts your draw offer"));
8551             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8552           } else {
8553             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8554           }
8555         }
8556     }
8557
8558
8559     /*
8560      * Look for thinking output
8561      */
8562     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8563           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8564                                 ) {
8565         int plylev, mvleft, mvtot, curscore, time;
8566         char mvname[MOVE_LEN];
8567         u64 nodes; // [DM]
8568         char plyext;
8569         int ignore = FALSE;
8570         int prefixHint = FALSE;
8571         mvname[0] = NULLCHAR;
8572
8573         switch (gameMode) {
8574           case MachinePlaysBlack:
8575           case IcsPlayingBlack:
8576             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8577             break;
8578           case MachinePlaysWhite:
8579           case IcsPlayingWhite:
8580             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8581             break;
8582           case AnalyzeMode:
8583           case AnalyzeFile:
8584             break;
8585           case IcsObserving: /* [DM] icsEngineAnalyze */
8586             if (!appData.icsEngineAnalyze) ignore = TRUE;
8587             break;
8588           case TwoMachinesPlay:
8589             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8590                 ignore = TRUE;
8591             }
8592             break;
8593           default:
8594             ignore = TRUE;
8595             break;
8596         }
8597
8598         if (!ignore) {
8599             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8600             buf1[0] = NULLCHAR;
8601             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8602                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8603
8604                 if (plyext != ' ' && plyext != '\t') {
8605                     time *= 100;
8606                 }
8607
8608                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8609                 if( cps->scoreIsAbsolute &&
8610                     ( gameMode == MachinePlaysBlack ||
8611                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8612                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8613                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8614                      !WhiteOnMove(currentMove)
8615                     ) )
8616                 {
8617                     curscore = -curscore;
8618                 }
8619
8620                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8621
8622                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8623                         char buf[MSG_SIZ];
8624                         FILE *f;
8625                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8626                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8627                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8628                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8629                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8630                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8631                                 fclose(f);
8632                         } else DisplayError(_("failed writing PV"), 0);
8633                 }
8634
8635                 tempStats.depth = plylev;
8636                 tempStats.nodes = nodes;
8637                 tempStats.time = time;
8638                 tempStats.score = curscore;
8639                 tempStats.got_only_move = 0;
8640
8641                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8642                         int ticklen;
8643
8644                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8645                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8646                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8647                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8648                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8649                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8650                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8651                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8652                 }
8653
8654                 /* Buffer overflow protection */
8655                 if (pv[0] != NULLCHAR) {
8656                     if (strlen(pv) >= sizeof(tempStats.movelist)
8657                         && appData.debugMode) {
8658                         fprintf(debugFP,
8659                                 "PV is too long; using the first %u bytes.\n",
8660                                 (unsigned) sizeof(tempStats.movelist) - 1);
8661                     }
8662
8663                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8664                 } else {
8665                     sprintf(tempStats.movelist, " no PV\n");
8666                 }
8667
8668                 if (tempStats.seen_stat) {
8669                     tempStats.ok_to_send = 1;
8670                 }
8671
8672                 if (strchr(tempStats.movelist, '(') != NULL) {
8673                     tempStats.line_is_book = 1;
8674                     tempStats.nr_moves = 0;
8675                     tempStats.moves_left = 0;
8676                 } else {
8677                     tempStats.line_is_book = 0;
8678                 }
8679
8680                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8681                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8682
8683                 SendProgramStatsToFrontend( cps, &tempStats );
8684
8685                 /*
8686                     [AS] Protect the thinkOutput buffer from overflow... this
8687                     is only useful if buf1 hasn't overflowed first!
8688                 */
8689                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8690                          plylev,
8691                          (gameMode == TwoMachinesPlay ?
8692                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8693                          ((double) curscore) / 100.0,
8694                          prefixHint ? lastHint : "",
8695                          prefixHint ? " " : "" );
8696
8697                 if( buf1[0] != NULLCHAR ) {
8698                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8699
8700                     if( strlen(pv) > max_len ) {
8701                         if( appData.debugMode) {
8702                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8703                         }
8704                         pv[max_len+1] = '\0';
8705                     }
8706
8707                     strcat( thinkOutput, pv);
8708                 }
8709
8710                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8711                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8712                     DisplayMove(currentMove - 1);
8713                 }
8714                 return;
8715
8716             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8717                 /* crafty (9.25+) says "(only move) <move>"
8718                  * if there is only 1 legal move
8719                  */
8720                 sscanf(p, "(only move) %s", buf1);
8721                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8722                 sprintf(programStats.movelist, "%s (only move)", buf1);
8723                 programStats.depth = 1;
8724                 programStats.nr_moves = 1;
8725                 programStats.moves_left = 1;
8726                 programStats.nodes = 1;
8727                 programStats.time = 1;
8728                 programStats.got_only_move = 1;
8729
8730                 /* Not really, but we also use this member to
8731                    mean "line isn't going to change" (Crafty
8732                    isn't searching, so stats won't change) */
8733                 programStats.line_is_book = 1;
8734
8735                 SendProgramStatsToFrontend( cps, &programStats );
8736
8737                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8738                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8739                     DisplayMove(currentMove - 1);
8740                 }
8741                 return;
8742             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8743                               &time, &nodes, &plylev, &mvleft,
8744                               &mvtot, mvname) >= 5) {
8745                 /* The stat01: line is from Crafty (9.29+) in response
8746                    to the "." command */
8747                 programStats.seen_stat = 1;
8748                 cps->maybeThinking = TRUE;
8749
8750                 if (programStats.got_only_move || !appData.periodicUpdates)
8751                   return;
8752
8753                 programStats.depth = plylev;
8754                 programStats.time = time;
8755                 programStats.nodes = nodes;
8756                 programStats.moves_left = mvleft;
8757                 programStats.nr_moves = mvtot;
8758                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8759                 programStats.ok_to_send = 1;
8760                 programStats.movelist[0] = '\0';
8761
8762                 SendProgramStatsToFrontend( cps, &programStats );
8763
8764                 return;
8765
8766             } else if (strncmp(message,"++",2) == 0) {
8767                 /* Crafty 9.29+ outputs this */
8768                 programStats.got_fail = 2;
8769                 return;
8770
8771             } else if (strncmp(message,"--",2) == 0) {
8772                 /* Crafty 9.29+ outputs this */
8773                 programStats.got_fail = 1;
8774                 return;
8775
8776             } else if (thinkOutput[0] != NULLCHAR &&
8777                        strncmp(message, "    ", 4) == 0) {
8778                 unsigned message_len;
8779
8780                 p = message;
8781                 while (*p && *p == ' ') p++;
8782
8783                 message_len = strlen( p );
8784
8785                 /* [AS] Avoid buffer overflow */
8786                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8787                     strcat(thinkOutput, " ");
8788                     strcat(thinkOutput, p);
8789                 }
8790
8791                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8792                     strcat(programStats.movelist, " ");
8793                     strcat(programStats.movelist, p);
8794                 }
8795
8796                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8797                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8798                     DisplayMove(currentMove - 1);
8799                 }
8800                 return;
8801             }
8802         }
8803         else {
8804             buf1[0] = NULLCHAR;
8805
8806             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8807                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8808             {
8809                 ChessProgramStats cpstats;
8810
8811                 if (plyext != ' ' && plyext != '\t') {
8812                     time *= 100;
8813                 }
8814
8815                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8816                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8817                     curscore = -curscore;
8818                 }
8819
8820                 cpstats.depth = plylev;
8821                 cpstats.nodes = nodes;
8822                 cpstats.time = time;
8823                 cpstats.score = curscore;
8824                 cpstats.got_only_move = 0;
8825                 cpstats.movelist[0] = '\0';
8826
8827                 if (buf1[0] != NULLCHAR) {
8828                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8829                 }
8830
8831                 cpstats.ok_to_send = 0;
8832                 cpstats.line_is_book = 0;
8833                 cpstats.nr_moves = 0;
8834                 cpstats.moves_left = 0;
8835
8836                 SendProgramStatsToFrontend( cps, &cpstats );
8837             }
8838         }
8839     }
8840 }
8841
8842
8843 /* Parse a game score from the character string "game", and
8844    record it as the history of the current game.  The game
8845    score is NOT assumed to start from the standard position.
8846    The display is not updated in any way.
8847    */
8848 void
8849 ParseGameHistory (char *game)
8850 {
8851     ChessMove moveType;
8852     int fromX, fromY, toX, toY, boardIndex;
8853     char promoChar;
8854     char *p, *q;
8855     char buf[MSG_SIZ];
8856
8857     if (appData.debugMode)
8858       fprintf(debugFP, "Parsing game history: %s\n", game);
8859
8860     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8861     gameInfo.site = StrSave(appData.icsHost);
8862     gameInfo.date = PGNDate();
8863     gameInfo.round = StrSave("-");
8864
8865     /* Parse out names of players */
8866     while (*game == ' ') game++;
8867     p = buf;
8868     while (*game != ' ') *p++ = *game++;
8869     *p = NULLCHAR;
8870     gameInfo.white = StrSave(buf);
8871     while (*game == ' ') game++;
8872     p = buf;
8873     while (*game != ' ' && *game != '\n') *p++ = *game++;
8874     *p = NULLCHAR;
8875     gameInfo.black = StrSave(buf);
8876
8877     /* Parse moves */
8878     boardIndex = blackPlaysFirst ? 1 : 0;
8879     yynewstr(game);
8880     for (;;) {
8881         yyboardindex = boardIndex;
8882         moveType = (ChessMove) Myylex();
8883         switch (moveType) {
8884           case IllegalMove:             /* maybe suicide chess, etc. */
8885   if (appData.debugMode) {
8886     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8887     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8888     setbuf(debugFP, NULL);
8889   }
8890           case WhitePromotion:
8891           case BlackPromotion:
8892           case WhiteNonPromotion:
8893           case BlackNonPromotion:
8894           case NormalMove:
8895           case WhiteCapturesEnPassant:
8896           case BlackCapturesEnPassant:
8897           case WhiteKingSideCastle:
8898           case WhiteQueenSideCastle:
8899           case BlackKingSideCastle:
8900           case BlackQueenSideCastle:
8901           case WhiteKingSideCastleWild:
8902           case WhiteQueenSideCastleWild:
8903           case BlackKingSideCastleWild:
8904           case BlackQueenSideCastleWild:
8905           /* PUSH Fabien */
8906           case WhiteHSideCastleFR:
8907           case WhiteASideCastleFR:
8908           case BlackHSideCastleFR:
8909           case BlackASideCastleFR:
8910           /* POP Fabien */
8911             fromX = currentMoveString[0] - AAA;
8912             fromY = currentMoveString[1] - ONE;
8913             toX = currentMoveString[2] - AAA;
8914             toY = currentMoveString[3] - ONE;
8915             promoChar = currentMoveString[4];
8916             break;
8917           case WhiteDrop:
8918           case BlackDrop:
8919             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8920             fromX = moveType == WhiteDrop ?
8921               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8922             (int) CharToPiece(ToLower(currentMoveString[0]));
8923             fromY = DROP_RANK;
8924             toX = currentMoveString[2] - AAA;
8925             toY = currentMoveString[3] - ONE;
8926             promoChar = NULLCHAR;
8927             break;
8928           case AmbiguousMove:
8929             /* bug? */
8930             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8931   if (appData.debugMode) {
8932     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8933     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8934     setbuf(debugFP, NULL);
8935   }
8936             DisplayError(buf, 0);
8937             return;
8938           case ImpossibleMove:
8939             /* bug? */
8940             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8941   if (appData.debugMode) {
8942     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8943     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8944     setbuf(debugFP, NULL);
8945   }
8946             DisplayError(buf, 0);
8947             return;
8948           case EndOfFile:
8949             if (boardIndex < backwardMostMove) {
8950                 /* Oops, gap.  How did that happen? */
8951                 DisplayError(_("Gap in move list"), 0);
8952                 return;
8953             }
8954             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8955             if (boardIndex > forwardMostMove) {
8956                 forwardMostMove = boardIndex;
8957             }
8958             return;
8959           case ElapsedTime:
8960             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8961                 strcat(parseList[boardIndex-1], " ");
8962                 strcat(parseList[boardIndex-1], yy_text);
8963             }
8964             continue;
8965           case Comment:
8966           case PGNTag:
8967           case NAG:
8968           default:
8969             /* ignore */
8970             continue;
8971           case WhiteWins:
8972           case BlackWins:
8973           case GameIsDrawn:
8974           case GameUnfinished:
8975             if (gameMode == IcsExamining) {
8976                 if (boardIndex < backwardMostMove) {
8977                     /* Oops, gap.  How did that happen? */
8978                     return;
8979                 }
8980                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8981                 return;
8982             }
8983             gameInfo.result = moveType;
8984             p = strchr(yy_text, '{');
8985             if (p == NULL) p = strchr(yy_text, '(');
8986             if (p == NULL) {
8987                 p = yy_text;
8988                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8989             } else {
8990                 q = strchr(p, *p == '{' ? '}' : ')');
8991                 if (q != NULL) *q = NULLCHAR;
8992                 p++;
8993             }
8994             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8995             gameInfo.resultDetails = StrSave(p);
8996             continue;
8997         }
8998         if (boardIndex >= forwardMostMove &&
8999             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9000             backwardMostMove = blackPlaysFirst ? 1 : 0;
9001             return;
9002         }
9003         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9004                                  fromY, fromX, toY, toX, promoChar,
9005                                  parseList[boardIndex]);
9006         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9007         /* currentMoveString is set as a side-effect of yylex */
9008         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9009         strcat(moveList[boardIndex], "\n");
9010         boardIndex++;
9011         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9012         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9013           case MT_NONE:
9014           case MT_STALEMATE:
9015           default:
9016             break;
9017           case MT_CHECK:
9018             if(gameInfo.variant != VariantShogi)
9019                 strcat(parseList[boardIndex - 1], "+");
9020             break;
9021           case MT_CHECKMATE:
9022           case MT_STAINMATE:
9023             strcat(parseList[boardIndex - 1], "#");
9024             break;
9025         }
9026     }
9027 }
9028
9029
9030 /* Apply a move to the given board  */
9031 void
9032 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9033 {
9034   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9035   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9036
9037     /* [HGM] compute & store e.p. status and castling rights for new position */
9038     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9039
9040       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9041       oldEP = (signed char)board[EP_STATUS];
9042       board[EP_STATUS] = EP_NONE;
9043
9044   if (fromY == DROP_RANK) {
9045         /* must be first */
9046         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9047             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9048             return;
9049         }
9050         piece = board[toY][toX] = (ChessSquare) fromX;
9051   } else {
9052       int i;
9053
9054       if( board[toY][toX] != EmptySquare )
9055            board[EP_STATUS] = EP_CAPTURE;
9056
9057       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9058            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9059                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9060       } else
9061       if( board[fromY][fromX] == WhitePawn ) {
9062            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9063                board[EP_STATUS] = EP_PAWN_MOVE;
9064            if( toY-fromY==2) {
9065                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9066                         gameInfo.variant != VariantBerolina || toX < fromX)
9067                       board[EP_STATUS] = toX | berolina;
9068                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9069                         gameInfo.variant != VariantBerolina || toX > fromX)
9070                       board[EP_STATUS] = toX;
9071            }
9072       } else
9073       if( board[fromY][fromX] == BlackPawn ) {
9074            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9075                board[EP_STATUS] = EP_PAWN_MOVE;
9076            if( toY-fromY== -2) {
9077                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9078                         gameInfo.variant != VariantBerolina || toX < fromX)
9079                       board[EP_STATUS] = toX | berolina;
9080                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9081                         gameInfo.variant != VariantBerolina || toX > fromX)
9082                       board[EP_STATUS] = toX;
9083            }
9084        }
9085
9086        for(i=0; i<nrCastlingRights; i++) {
9087            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9088               board[CASTLING][i] == toX   && castlingRank[i] == toY
9089              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9090        }
9091
9092      if (fromX == toX && fromY == toY) return;
9093
9094      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9095      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9096      if(gameInfo.variant == VariantKnightmate)
9097          king += (int) WhiteUnicorn - (int) WhiteKing;
9098
9099     /* Code added by Tord: */
9100     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9101     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9102         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9103       board[fromY][fromX] = EmptySquare;
9104       board[toY][toX] = EmptySquare;
9105       if((toX > fromX) != (piece == WhiteRook)) {
9106         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9107       } else {
9108         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9109       }
9110     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9111                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9112       board[fromY][fromX] = EmptySquare;
9113       board[toY][toX] = EmptySquare;
9114       if((toX > fromX) != (piece == BlackRook)) {
9115         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9116       } else {
9117         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9118       }
9119     /* End of code added by Tord */
9120
9121     } else if (board[fromY][fromX] == king
9122         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9123         && toY == fromY && toX > fromX+1) {
9124         board[fromY][fromX] = EmptySquare;
9125         board[toY][toX] = king;
9126         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9127         board[fromY][BOARD_RGHT-1] = EmptySquare;
9128     } else if (board[fromY][fromX] == king
9129         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9130                && toY == fromY && toX < fromX-1) {
9131         board[fromY][fromX] = EmptySquare;
9132         board[toY][toX] = king;
9133         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9134         board[fromY][BOARD_LEFT] = EmptySquare;
9135     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9136                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9137                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9138                ) {
9139         /* white pawn promotion */
9140         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9141         if(gameInfo.variant==VariantBughouse ||
9142            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9143             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9144         board[fromY][fromX] = EmptySquare;
9145     } else if ((fromY >= BOARD_HEIGHT>>1)
9146                && (toX != fromX)
9147                && gameInfo.variant != VariantXiangqi
9148                && gameInfo.variant != VariantBerolina
9149                && (board[fromY][fromX] == WhitePawn)
9150                && (board[toY][toX] == EmptySquare)) {
9151         board[fromY][fromX] = EmptySquare;
9152         board[toY][toX] = WhitePawn;
9153         captured = board[toY - 1][toX];
9154         board[toY - 1][toX] = EmptySquare;
9155     } else if ((fromY == BOARD_HEIGHT-4)
9156                && (toX == fromX)
9157                && gameInfo.variant == VariantBerolina
9158                && (board[fromY][fromX] == WhitePawn)
9159                && (board[toY][toX] == EmptySquare)) {
9160         board[fromY][fromX] = EmptySquare;
9161         board[toY][toX] = WhitePawn;
9162         if(oldEP & EP_BEROLIN_A) {
9163                 captured = board[fromY][fromX-1];
9164                 board[fromY][fromX-1] = EmptySquare;
9165         }else{  captured = board[fromY][fromX+1];
9166                 board[fromY][fromX+1] = EmptySquare;
9167         }
9168     } else if (board[fromY][fromX] == king
9169         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9170                && toY == fromY && toX > fromX+1) {
9171         board[fromY][fromX] = EmptySquare;
9172         board[toY][toX] = king;
9173         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9174         board[fromY][BOARD_RGHT-1] = EmptySquare;
9175     } else if (board[fromY][fromX] == king
9176         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9177                && toY == fromY && toX < fromX-1) {
9178         board[fromY][fromX] = EmptySquare;
9179         board[toY][toX] = king;
9180         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9181         board[fromY][BOARD_LEFT] = EmptySquare;
9182     } else if (fromY == 7 && fromX == 3
9183                && board[fromY][fromX] == BlackKing
9184                && toY == 7 && toX == 5) {
9185         board[fromY][fromX] = EmptySquare;
9186         board[toY][toX] = BlackKing;
9187         board[fromY][7] = EmptySquare;
9188         board[toY][4] = BlackRook;
9189     } else if (fromY == 7 && fromX == 3
9190                && board[fromY][fromX] == BlackKing
9191                && toY == 7 && toX == 1) {
9192         board[fromY][fromX] = EmptySquare;
9193         board[toY][toX] = BlackKing;
9194         board[fromY][0] = EmptySquare;
9195         board[toY][2] = BlackRook;
9196     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9197                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9198                && toY < promoRank && promoChar
9199                ) {
9200         /* black pawn promotion */
9201         board[toY][toX] = CharToPiece(ToLower(promoChar));
9202         if(gameInfo.variant==VariantBughouse ||
9203            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9204             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9205         board[fromY][fromX] = EmptySquare;
9206     } else if ((fromY < BOARD_HEIGHT>>1)
9207                && (toX != fromX)
9208                && gameInfo.variant != VariantXiangqi
9209                && gameInfo.variant != VariantBerolina
9210                && (board[fromY][fromX] == BlackPawn)
9211                && (board[toY][toX] == EmptySquare)) {
9212         board[fromY][fromX] = EmptySquare;
9213         board[toY][toX] = BlackPawn;
9214         captured = board[toY + 1][toX];
9215         board[toY + 1][toX] = EmptySquare;
9216     } else if ((fromY == 3)
9217                && (toX == fromX)
9218                && gameInfo.variant == VariantBerolina
9219                && (board[fromY][fromX] == BlackPawn)
9220                && (board[toY][toX] == EmptySquare)) {
9221         board[fromY][fromX] = EmptySquare;
9222         board[toY][toX] = BlackPawn;
9223         if(oldEP & EP_BEROLIN_A) {
9224                 captured = board[fromY][fromX-1];
9225                 board[fromY][fromX-1] = EmptySquare;
9226         }else{  captured = board[fromY][fromX+1];
9227                 board[fromY][fromX+1] = EmptySquare;
9228         }
9229     } else {
9230         board[toY][toX] = board[fromY][fromX];
9231         board[fromY][fromX] = EmptySquare;
9232     }
9233   }
9234
9235     if (gameInfo.holdingsWidth != 0) {
9236
9237       /* !!A lot more code needs to be written to support holdings  */
9238       /* [HGM] OK, so I have written it. Holdings are stored in the */
9239       /* penultimate board files, so they are automaticlly stored   */
9240       /* in the game history.                                       */
9241       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9242                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9243         /* Delete from holdings, by decreasing count */
9244         /* and erasing image if necessary            */
9245         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9246         if(p < (int) BlackPawn) { /* white drop */
9247              p -= (int)WhitePawn;
9248                  p = PieceToNumber((ChessSquare)p);
9249              if(p >= gameInfo.holdingsSize) p = 0;
9250              if(--board[p][BOARD_WIDTH-2] <= 0)
9251                   board[p][BOARD_WIDTH-1] = EmptySquare;
9252              if((int)board[p][BOARD_WIDTH-2] < 0)
9253                         board[p][BOARD_WIDTH-2] = 0;
9254         } else {                  /* black drop */
9255              p -= (int)BlackPawn;
9256                  p = PieceToNumber((ChessSquare)p);
9257              if(p >= gameInfo.holdingsSize) p = 0;
9258              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9259                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9260              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9261                         board[BOARD_HEIGHT-1-p][1] = 0;
9262         }
9263       }
9264       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9265           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9266         /* [HGM] holdings: Add to holdings, if holdings exist */
9267         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9268                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9269                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9270         }
9271         p = (int) captured;
9272         if (p >= (int) BlackPawn) {
9273           p -= (int)BlackPawn;
9274           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9275                   /* in Shogi restore piece to its original  first */
9276                   captured = (ChessSquare) (DEMOTED captured);
9277                   p = DEMOTED p;
9278           }
9279           p = PieceToNumber((ChessSquare)p);
9280           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9281           board[p][BOARD_WIDTH-2]++;
9282           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9283         } else {
9284           p -= (int)WhitePawn;
9285           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9286                   captured = (ChessSquare) (DEMOTED captured);
9287                   p = DEMOTED p;
9288           }
9289           p = PieceToNumber((ChessSquare)p);
9290           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9291           board[BOARD_HEIGHT-1-p][1]++;
9292           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9293         }
9294       }
9295     } else if (gameInfo.variant == VariantAtomic) {
9296       if (captured != EmptySquare) {
9297         int y, x;
9298         for (y = toY-1; y <= toY+1; y++) {
9299           for (x = toX-1; x <= toX+1; x++) {
9300             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9301                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9302               board[y][x] = EmptySquare;
9303             }
9304           }
9305         }
9306         board[toY][toX] = EmptySquare;
9307       }
9308     }
9309     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9310         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9311     } else
9312     if(promoChar == '+') {
9313         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9314         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9315     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9316         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9317     }
9318     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9319                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9320         // [HGM] superchess: take promotion piece out of holdings
9321         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9322         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9323             if(!--board[k][BOARD_WIDTH-2])
9324                 board[k][BOARD_WIDTH-1] = EmptySquare;
9325         } else {
9326             if(!--board[BOARD_HEIGHT-1-k][1])
9327                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9328         }
9329     }
9330
9331 }
9332
9333 /* Updates forwardMostMove */
9334 void
9335 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9336 {
9337 //    forwardMostMove++; // [HGM] bare: moved downstream
9338
9339     (void) CoordsToAlgebraic(boards[forwardMostMove],
9340                              PosFlags(forwardMostMove),
9341                              fromY, fromX, toY, toX, promoChar,
9342                              parseList[forwardMostMove]);
9343
9344     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9345         int timeLeft; static int lastLoadFlag=0; int king, piece;
9346         piece = boards[forwardMostMove][fromY][fromX];
9347         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9348         if(gameInfo.variant == VariantKnightmate)
9349             king += (int) WhiteUnicorn - (int) WhiteKing;
9350         if(forwardMostMove == 0) {
9351             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9352                 fprintf(serverMoves, "%s;", UserName());
9353             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9354                 fprintf(serverMoves, "%s;", second.tidy);
9355             fprintf(serverMoves, "%s;", first.tidy);
9356             if(gameMode == MachinePlaysWhite)
9357                 fprintf(serverMoves, "%s;", UserName());
9358             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9359                 fprintf(serverMoves, "%s;", second.tidy);
9360         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9361         lastLoadFlag = loadFlag;
9362         // print base move
9363         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9364         // print castling suffix
9365         if( toY == fromY && piece == king ) {
9366             if(toX-fromX > 1)
9367                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9368             if(fromX-toX >1)
9369                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9370         }
9371         // e.p. suffix
9372         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9373              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9374              boards[forwardMostMove][toY][toX] == EmptySquare
9375              && fromX != toX && fromY != toY)
9376                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9377         // promotion suffix
9378         if(promoChar != NULLCHAR)
9379                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9380         if(!loadFlag) {
9381                 char buf[MOVE_LEN*2], *p; int len;
9382             fprintf(serverMoves, "/%d/%d",
9383                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9384             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9385             else                      timeLeft = blackTimeRemaining/1000;
9386             fprintf(serverMoves, "/%d", timeLeft);
9387                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9388                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9389                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9390             fprintf(serverMoves, "/%s", buf);
9391         }
9392         fflush(serverMoves);
9393     }
9394
9395     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9396         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9397       return;
9398     }
9399     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9400     if (commentList[forwardMostMove+1] != NULL) {
9401         free(commentList[forwardMostMove+1]);
9402         commentList[forwardMostMove+1] = NULL;
9403     }
9404     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9405     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9406     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9407     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9408     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9409     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9410     adjustedClock = FALSE;
9411     gameInfo.result = GameUnfinished;
9412     if (gameInfo.resultDetails != NULL) {
9413         free(gameInfo.resultDetails);
9414         gameInfo.resultDetails = NULL;
9415     }
9416     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9417                               moveList[forwardMostMove - 1]);
9418     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9419       case MT_NONE:
9420       case MT_STALEMATE:
9421       default:
9422         break;
9423       case MT_CHECK:
9424         if(gameInfo.variant != VariantShogi)
9425             strcat(parseList[forwardMostMove - 1], "+");
9426         break;
9427       case MT_CHECKMATE:
9428       case MT_STAINMATE:
9429         strcat(parseList[forwardMostMove - 1], "#");
9430         break;
9431     }
9432     if (appData.debugMode) {
9433         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9434     }
9435
9436 }
9437
9438 /* Updates currentMove if not pausing */
9439 void
9440 ShowMove (int fromX, int fromY, int toX, int toY)
9441 {
9442     int instant = (gameMode == PlayFromGameFile) ?
9443         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9444     if(appData.noGUI) return;
9445     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9446         if (!instant) {
9447             if (forwardMostMove == currentMove + 1) {
9448                 AnimateMove(boards[forwardMostMove - 1],
9449                             fromX, fromY, toX, toY);
9450             }
9451             if (appData.highlightLastMove) {
9452                 SetHighlights(fromX, fromY, toX, toY);
9453             }
9454         }
9455         currentMove = forwardMostMove;
9456     }
9457
9458     if (instant) return;
9459
9460     DisplayMove(currentMove - 1);
9461     DrawPosition(FALSE, boards[currentMove]);
9462     DisplayBothClocks();
9463     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9464 }
9465
9466 void
9467 SendEgtPath (ChessProgramState *cps)
9468 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9469         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9470
9471         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9472
9473         while(*p) {
9474             char c, *q = name+1, *r, *s;
9475
9476             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9477             while(*p && *p != ',') *q++ = *p++;
9478             *q++ = ':'; *q = 0;
9479             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9480                 strcmp(name, ",nalimov:") == 0 ) {
9481                 // take nalimov path from the menu-changeable option first, if it is defined
9482               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9483                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9484             } else
9485             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9486                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9487                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9488                 s = r = StrStr(s, ":") + 1; // beginning of path info
9489                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9490                 c = *r; *r = 0;             // temporarily null-terminate path info
9491                     *--q = 0;               // strip of trailig ':' from name
9492                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9493                 *r = c;
9494                 SendToProgram(buf,cps);     // send egtbpath command for this format
9495             }
9496             if(*p == ',') p++; // read away comma to position for next format name
9497         }
9498 }
9499
9500 void
9501 InitChessProgram (ChessProgramState *cps, int setup)
9502 /* setup needed to setup FRC opening position */
9503 {
9504     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9505     if (appData.noChessProgram) return;
9506     hintRequested = FALSE;
9507     bookRequested = FALSE;
9508
9509     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9510     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9511     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9512     if(cps->memSize) { /* [HGM] memory */
9513       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9514         SendToProgram(buf, cps);
9515     }
9516     SendEgtPath(cps); /* [HGM] EGT */
9517     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9518       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9519         SendToProgram(buf, cps);
9520     }
9521
9522     SendToProgram(cps->initString, cps);
9523     if (gameInfo.variant != VariantNormal &&
9524         gameInfo.variant != VariantLoadable
9525         /* [HGM] also send variant if board size non-standard */
9526         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9527                                             ) {
9528       char *v = VariantName(gameInfo.variant);
9529       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9530         /* [HGM] in protocol 1 we have to assume all variants valid */
9531         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9532         DisplayFatalError(buf, 0, 1);
9533         return;
9534       }
9535
9536       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9537       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9538       if( gameInfo.variant == VariantXiangqi )
9539            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9540       if( gameInfo.variant == VariantShogi )
9541            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9542       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9543            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9544       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9545           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9546            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9547       if( gameInfo.variant == VariantCourier )
9548            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9549       if( gameInfo.variant == VariantSuper )
9550            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9551       if( gameInfo.variant == VariantGreat )
9552            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9553       if( gameInfo.variant == VariantSChess )
9554            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9555       if( gameInfo.variant == VariantGrand )
9556            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9557
9558       if(overruled) {
9559         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9560                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9561            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9562            if(StrStr(cps->variants, b) == NULL) {
9563                // specific sized variant not known, check if general sizing allowed
9564                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9565                    if(StrStr(cps->variants, "boardsize") == NULL) {
9566                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9567                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9568                        DisplayFatalError(buf, 0, 1);
9569                        return;
9570                    }
9571                    /* [HGM] here we really should compare with the maximum supported board size */
9572                }
9573            }
9574       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9575       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9576       SendToProgram(buf, cps);
9577     }
9578     currentlyInitializedVariant = gameInfo.variant;
9579
9580     /* [HGM] send opening position in FRC to first engine */
9581     if(setup) {
9582           SendToProgram("force\n", cps);
9583           SendBoard(cps, 0);
9584           /* engine is now in force mode! Set flag to wake it up after first move. */
9585           setboardSpoiledMachineBlack = 1;
9586     }
9587
9588     if (cps->sendICS) {
9589       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9590       SendToProgram(buf, cps);
9591     }
9592     cps->maybeThinking = FALSE;
9593     cps->offeredDraw = 0;
9594     if (!appData.icsActive) {
9595         SendTimeControl(cps, movesPerSession, timeControl,
9596                         timeIncrement, appData.searchDepth,
9597                         searchTime);
9598     }
9599     if (appData.showThinking
9600         // [HGM] thinking: four options require thinking output to be sent
9601         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9602                                 ) {
9603         SendToProgram("post\n", cps);
9604     }
9605     SendToProgram("hard\n", cps);
9606     if (!appData.ponderNextMove) {
9607         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9608            it without being sure what state we are in first.  "hard"
9609            is not a toggle, so that one is OK.
9610          */
9611         SendToProgram("easy\n", cps);
9612     }
9613     if (cps->usePing) {
9614       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9615       SendToProgram(buf, cps);
9616     }
9617     cps->initDone = TRUE;
9618     ClearEngineOutputPane(cps == &second);
9619 }
9620
9621
9622 void
9623 StartChessProgram (ChessProgramState *cps)
9624 {
9625     char buf[MSG_SIZ];
9626     int err;
9627
9628     if (appData.noChessProgram) return;
9629     cps->initDone = FALSE;
9630
9631     if (strcmp(cps->host, "localhost") == 0) {
9632         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9633     } else if (*appData.remoteShell == NULLCHAR) {
9634         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9635     } else {
9636         if (*appData.remoteUser == NULLCHAR) {
9637           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9638                     cps->program);
9639         } else {
9640           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9641                     cps->host, appData.remoteUser, cps->program);
9642         }
9643         err = StartChildProcess(buf, "", &cps->pr);
9644     }
9645
9646     if (err != 0) {
9647       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9648         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9649         if(cps != &first) return;
9650         appData.noChessProgram = TRUE;
9651         ThawUI();
9652         SetNCPMode();
9653 //      DisplayFatalError(buf, err, 1);
9654 //      cps->pr = NoProc;
9655 //      cps->isr = NULL;
9656         return;
9657     }
9658
9659     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9660     if (cps->protocolVersion > 1) {
9661       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9662       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9663       cps->comboCnt = 0;  //                and values of combo boxes
9664       SendToProgram(buf, cps);
9665     } else {
9666       SendToProgram("xboard\n", cps);
9667     }
9668 }
9669
9670 void
9671 TwoMachinesEventIfReady P((void))
9672 {
9673   static int curMess = 0;
9674   if (first.lastPing != first.lastPong) {
9675     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9676     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9677     return;
9678   }
9679   if (second.lastPing != second.lastPong) {
9680     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9681     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9682     return;
9683   }
9684   DisplayMessage("", ""); curMess = 0;
9685   ThawUI();
9686   TwoMachinesEvent();
9687 }
9688
9689 char *
9690 MakeName (char *template)
9691 {
9692     time_t clock;
9693     struct tm *tm;
9694     static char buf[MSG_SIZ];
9695     char *p = buf;
9696     int i;
9697
9698     clock = time((time_t *)NULL);
9699     tm = localtime(&clock);
9700
9701     while(*p++ = *template++) if(p[-1] == '%') {
9702         switch(*template++) {
9703           case 0:   *p = 0; return buf;
9704           case 'Y': i = tm->tm_year+1900; break;
9705           case 'y': i = tm->tm_year-100; break;
9706           case 'M': i = tm->tm_mon+1; break;
9707           case 'd': i = tm->tm_mday; break;
9708           case 'h': i = tm->tm_hour; break;
9709           case 'm': i = tm->tm_min; break;
9710           case 's': i = tm->tm_sec; break;
9711           default:  i = 0;
9712         }
9713         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9714     }
9715     return buf;
9716 }
9717
9718 int
9719 CountPlayers (char *p)
9720 {
9721     int n = 0;
9722     while(p = strchr(p, '\n')) p++, n++; // count participants
9723     return n;
9724 }
9725
9726 FILE *
9727 WriteTourneyFile (char *results, FILE *f)
9728 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9729     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9730     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9731         // create a file with tournament description
9732         fprintf(f, "-participants {%s}\n", appData.participants);
9733         fprintf(f, "-seedBase %d\n", appData.seedBase);
9734         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9735         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9736         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9737         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9738         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9739         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9740         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9741         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9742         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9743         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9744         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9745         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9746         if(searchTime > 0)
9747                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9748         else {
9749                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9750                 fprintf(f, "-tc %s\n", appData.timeControl);
9751                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9752         }
9753         fprintf(f, "-results \"%s\"\n", results);
9754     }
9755     return f;
9756 }
9757
9758 #define MAXENGINES 1000
9759 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9760
9761 void
9762 Substitute (char *participants, int expunge)
9763 {
9764     int i, changed, changes=0, nPlayers=0;
9765     char *p, *q, *r, buf[MSG_SIZ];
9766     if(participants == NULL) return;
9767     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9768     r = p = participants; q = appData.participants;
9769     while(*p && *p == *q) {
9770         if(*p == '\n') r = p+1, nPlayers++;
9771         p++; q++;
9772     }
9773     if(*p) { // difference
9774         while(*p && *p++ != '\n');
9775         while(*q && *q++ != '\n');
9776       changed = nPlayers;
9777         changes = 1 + (strcmp(p, q) != 0);
9778     }
9779     if(changes == 1) { // a single engine mnemonic was changed
9780         q = r; while(*q) nPlayers += (*q++ == '\n');
9781         p = buf; while(*r && (*p = *r++) != '\n') p++;
9782         *p = NULLCHAR;
9783         NamesToList(firstChessProgramNames, command, mnemonic);
9784         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9785         if(mnemonic[i]) { // The substitute is valid
9786             FILE *f;
9787             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9788                 flock(fileno(f), LOCK_EX);
9789                 ParseArgsFromFile(f);
9790                 fseek(f, 0, SEEK_SET);
9791                 FREE(appData.participants); appData.participants = participants;
9792                 if(expunge) { // erase results of replaced engine
9793                     int len = strlen(appData.results), w, b, dummy;
9794                     for(i=0; i<len; i++) {
9795                         Pairing(i, nPlayers, &w, &b, &dummy);
9796                         if((w == changed || b == changed) && appData.results[i] == '*') {
9797                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9798                             fclose(f);
9799                             return;
9800                         }
9801                     }
9802                     for(i=0; i<len; i++) {
9803                         Pairing(i, nPlayers, &w, &b, &dummy);
9804                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9805                     }
9806                 }
9807                 WriteTourneyFile(appData.results, f);
9808                 fclose(f); // release lock
9809                 return;
9810             }
9811         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9812     }
9813     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9814     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9815     free(participants);
9816     return;
9817 }
9818
9819 int
9820 CreateTourney (char *name)
9821 {
9822         FILE *f;
9823         if(matchMode && strcmp(name, appData.tourneyFile)) {
9824              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9825         }
9826         if(name[0] == NULLCHAR) {
9827             if(appData.participants[0])
9828                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9829             return 0;
9830         }
9831         f = fopen(name, "r");
9832         if(f) { // file exists
9833             ASSIGN(appData.tourneyFile, name);
9834             ParseArgsFromFile(f); // parse it
9835         } else {
9836             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9837             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9838                 DisplayError(_("Not enough participants"), 0);
9839                 return 0;
9840             }
9841             ASSIGN(appData.tourneyFile, name);
9842             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9843             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9844         }
9845         fclose(f);
9846         appData.noChessProgram = FALSE;
9847         appData.clockMode = TRUE;
9848         SetGNUMode();
9849         return 1;
9850 }
9851
9852 void
9853 NamesToList (char *names, char **engineList, char **engineMnemonic)
9854 {
9855     char buf[MSG_SIZ], *p, *q;
9856     int i=1;
9857     while(*names) {
9858         p = names; q = buf;
9859         while(*p && *p != '\n') *q++ = *p++;
9860         *q = 0;
9861         if(engineList[i]) free(engineList[i]);
9862         engineList[i] = strdup(buf);
9863         if(*p == '\n') p++;
9864         TidyProgramName(engineList[i], "localhost", buf);
9865         if(engineMnemonic[i]) free(engineMnemonic[i]);
9866         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9867             strcat(buf, " (");
9868             sscanf(q + 8, "%s", buf + strlen(buf));
9869             strcat(buf, ")");
9870         }
9871         engineMnemonic[i] = strdup(buf);
9872         names = p; i++;
9873       if(i > MAXENGINES - 2) break;
9874     }
9875     engineList[i] = engineMnemonic[i] = NULL;
9876 }
9877
9878 // following implemented as macro to avoid type limitations
9879 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9880
9881 void
9882 SwapEngines (int n)
9883 {   // swap settings for first engine and other engine (so far only some selected options)
9884     int h;
9885     char *p;
9886     if(n == 0) return;
9887     SWAP(directory, p)
9888     SWAP(chessProgram, p)
9889     SWAP(isUCI, h)
9890     SWAP(hasOwnBookUCI, h)
9891     SWAP(protocolVersion, h)
9892     SWAP(reuse, h)
9893     SWAP(scoreIsAbsolute, h)
9894     SWAP(timeOdds, h)
9895     SWAP(logo, p)
9896     SWAP(pgnName, p)
9897     SWAP(pvSAN, h)
9898     SWAP(engOptions, p)
9899 }
9900
9901 void
9902 SetPlayer (int player)
9903 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9904     int i;
9905     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9906     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9907     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9908     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9909     if(mnemonic[i]) {
9910         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9911         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9912         appData.firstHasOwnBookUCI = !appData.defNoBook;
9913         ParseArgsFromString(buf);
9914     }
9915     free(engineName);
9916 }
9917
9918 int
9919 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9920 {   // determine players from game number
9921     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9922
9923     if(appData.tourneyType == 0) {
9924         roundsPerCycle = (nPlayers - 1) | 1;
9925         pairingsPerRound = nPlayers / 2;
9926     } else if(appData.tourneyType > 0) {
9927         roundsPerCycle = nPlayers - appData.tourneyType;
9928         pairingsPerRound = appData.tourneyType;
9929     }
9930     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9931     gamesPerCycle = gamesPerRound * roundsPerCycle;
9932     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9933     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9934     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9935     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9936     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9937     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9938
9939     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9940     if(appData.roundSync) *syncInterval = gamesPerRound;
9941
9942     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9943
9944     if(appData.tourneyType == 0) {
9945         if(curPairing == (nPlayers-1)/2 ) {
9946             *whitePlayer = curRound;
9947             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9948         } else {
9949             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9950             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9951             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9952             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9953         }
9954     } else if(appData.tourneyType > 0) {
9955         *whitePlayer = curPairing;
9956         *blackPlayer = curRound + appData.tourneyType;
9957     }
9958
9959     // take care of white/black alternation per round. 
9960     // For cycles and games this is already taken care of by default, derived from matchGame!
9961     return curRound & 1;
9962 }
9963
9964 int
9965 NextTourneyGame (int nr, int *swapColors)
9966 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9967     char *p, *q;
9968     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9969     FILE *tf;
9970     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9971     tf = fopen(appData.tourneyFile, "r");
9972     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9973     ParseArgsFromFile(tf); fclose(tf);
9974     InitTimeControls(); // TC might be altered from tourney file
9975
9976     nPlayers = CountPlayers(appData.participants); // count participants
9977     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
9978     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9979
9980     if(syncInterval) {
9981         p = q = appData.results;
9982         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9983         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9984             DisplayMessage(_("Waiting for other game(s)"),"");
9985             waitingForGame = TRUE;
9986             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9987             return 0;
9988         }
9989         waitingForGame = FALSE;
9990     }
9991
9992     if(appData.tourneyType < 0) {
9993         if(nr>=0 && !pairingReceived) {
9994             char buf[1<<16];
9995             if(pairing.pr == NoProc) {
9996                 if(!appData.pairingEngine[0]) {
9997                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
9998                     return 0;
9999                 }
10000                 StartChessProgram(&pairing); // starts the pairing engine
10001             }
10002             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10003             SendToProgram(buf, &pairing);
10004             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10005             SendToProgram(buf, &pairing);
10006             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10007         }
10008         pairingReceived = 0;                              // ... so we continue here 
10009         *swapColors = 0;
10010         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10011         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10012         matchGame = 1; roundNr = nr / syncInterval + 1;
10013     }
10014
10015     if(first.pr != NoProc || second.pr != NoProc) return 1; // engines already loaded
10016
10017     // redefine engines, engine dir, etc.
10018     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10019     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
10020     SwapEngines(1);
10021     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
10022     SwapEngines(1);         // and make that valid for second engine by swapping
10023     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10024     InitEngine(&second, 1);
10025     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10026     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10027     return 1;
10028 }
10029
10030 void
10031 NextMatchGame ()
10032 {   // performs game initialization that does not invoke engines, and then tries to start the game
10033     int res, firstWhite, swapColors = 0;
10034     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10035     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10036     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10037     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10038     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10039     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10040     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10041     Reset(FALSE, first.pr != NoProc);
10042     res = LoadGameOrPosition(matchGame); // setup game
10043     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10044     if(!res) return; // abort when bad game/pos file
10045     TwoMachinesEvent();
10046 }
10047
10048 void
10049 UserAdjudicationEvent (int result)
10050 {
10051     ChessMove gameResult = GameIsDrawn;
10052
10053     if( result > 0 ) {
10054         gameResult = WhiteWins;
10055     }
10056     else if( result < 0 ) {
10057         gameResult = BlackWins;
10058     }
10059
10060     if( gameMode == TwoMachinesPlay ) {
10061         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10062     }
10063 }
10064
10065
10066 // [HGM] save: calculate checksum of game to make games easily identifiable
10067 int
10068 StringCheckSum (char *s)
10069 {
10070         int i = 0;
10071         if(s==NULL) return 0;
10072         while(*s) i = i*259 + *s++;
10073         return i;
10074 }
10075
10076 int
10077 GameCheckSum ()
10078 {
10079         int i, sum=0;
10080         for(i=backwardMostMove; i<forwardMostMove; i++) {
10081                 sum += pvInfoList[i].depth;
10082                 sum += StringCheckSum(parseList[i]);
10083                 sum += StringCheckSum(commentList[i]);
10084                 sum *= 261;
10085         }
10086         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10087         return sum + StringCheckSum(commentList[i]);
10088 } // end of save patch
10089
10090 void
10091 GameEnds (ChessMove result, char *resultDetails, int whosays)
10092 {
10093     GameMode nextGameMode;
10094     int isIcsGame;
10095     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10096
10097     if(endingGame) return; /* [HGM] crash: forbid recursion */
10098     endingGame = 1;
10099     if(twoBoards) { // [HGM] dual: switch back to one board
10100         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10101         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10102     }
10103     if (appData.debugMode) {
10104       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10105               result, resultDetails ? resultDetails : "(null)", whosays);
10106     }
10107
10108     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10109
10110     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10111         /* If we are playing on ICS, the server decides when the
10112            game is over, but the engine can offer to draw, claim
10113            a draw, or resign.
10114          */
10115 #if ZIPPY
10116         if (appData.zippyPlay && first.initDone) {
10117             if (result == GameIsDrawn) {
10118                 /* In case draw still needs to be claimed */
10119                 SendToICS(ics_prefix);
10120                 SendToICS("draw\n");
10121             } else if (StrCaseStr(resultDetails, "resign")) {
10122                 SendToICS(ics_prefix);
10123                 SendToICS("resign\n");
10124             }
10125         }
10126 #endif
10127         endingGame = 0; /* [HGM] crash */
10128         return;
10129     }
10130
10131     /* If we're loading the game from a file, stop */
10132     if (whosays == GE_FILE) {
10133       (void) StopLoadGameTimer();
10134       gameFileFP = NULL;
10135     }
10136
10137     /* Cancel draw offers */
10138     first.offeredDraw = second.offeredDraw = 0;
10139
10140     /* If this is an ICS game, only ICS can really say it's done;
10141        if not, anyone can. */
10142     isIcsGame = (gameMode == IcsPlayingWhite ||
10143                  gameMode == IcsPlayingBlack ||
10144                  gameMode == IcsObserving    ||
10145                  gameMode == IcsExamining);
10146
10147     if (!isIcsGame || whosays == GE_ICS) {
10148         /* OK -- not an ICS game, or ICS said it was done */
10149         StopClocks();
10150         if (!isIcsGame && !appData.noChessProgram)
10151           SetUserThinkingEnables();
10152
10153         /* [HGM] if a machine claims the game end we verify this claim */
10154         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10155             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10156                 char claimer;
10157                 ChessMove trueResult = (ChessMove) -1;
10158
10159                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10160                                             first.twoMachinesColor[0] :
10161                                             second.twoMachinesColor[0] ;
10162
10163                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10164                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10165                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10166                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10167                 } else
10168                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10169                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10170                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10171                 } else
10172                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10173                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10174                 }
10175
10176                 // now verify win claims, but not in drop games, as we don't understand those yet
10177                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10178                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10179                     (result == WhiteWins && claimer == 'w' ||
10180                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10181                       if (appData.debugMode) {
10182                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10183                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10184                       }
10185                       if(result != trueResult) {
10186                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10187                               result = claimer == 'w' ? BlackWins : WhiteWins;
10188                               resultDetails = buf;
10189                       }
10190                 } else
10191                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10192                     && (forwardMostMove <= backwardMostMove ||
10193                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10194                         (claimer=='b')==(forwardMostMove&1))
10195                                                                                   ) {
10196                       /* [HGM] verify: draws that were not flagged are false claims */
10197                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10198                       result = claimer == 'w' ? BlackWins : WhiteWins;
10199                       resultDetails = buf;
10200                 }
10201                 /* (Claiming a loss is accepted no questions asked!) */
10202             }
10203             /* [HGM] bare: don't allow bare King to win */
10204             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10205                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10206                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10207                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10208                && result != GameIsDrawn)
10209             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10210                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10211                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10212                         if(p >= 0 && p <= (int)WhiteKing) k++;
10213                 }
10214                 if (appData.debugMode) {
10215                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10216                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10217                 }
10218                 if(k <= 1) {
10219                         result = GameIsDrawn;
10220                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10221                         resultDetails = buf;
10222                 }
10223             }
10224         }
10225
10226
10227         if(serverMoves != NULL && !loadFlag) { char c = '=';
10228             if(result==WhiteWins) c = '+';
10229             if(result==BlackWins) c = '-';
10230             if(resultDetails != NULL)
10231                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10232         }
10233         if (resultDetails != NULL) {
10234             gameInfo.result = result;
10235             gameInfo.resultDetails = StrSave(resultDetails);
10236
10237             /* display last move only if game was not loaded from file */
10238             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10239                 DisplayMove(currentMove - 1);
10240
10241             if (forwardMostMove != 0) {
10242                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10243                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10244                                                                 ) {
10245                     if (*appData.saveGameFile != NULLCHAR) {
10246                         SaveGameToFile(appData.saveGameFile, TRUE);
10247                     } else if (appData.autoSaveGames) {
10248                         AutoSaveGame();
10249                     }
10250                     if (*appData.savePositionFile != NULLCHAR) {
10251                         SavePositionToFile(appData.savePositionFile);
10252                     }
10253                 }
10254             }
10255
10256             /* Tell program how game ended in case it is learning */
10257             /* [HGM] Moved this to after saving the PGN, just in case */
10258             /* engine died and we got here through time loss. In that */
10259             /* case we will get a fatal error writing the pipe, which */
10260             /* would otherwise lose us the PGN.                       */
10261             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10262             /* output during GameEnds should never be fatal anymore   */
10263             if (gameMode == MachinePlaysWhite ||
10264                 gameMode == MachinePlaysBlack ||
10265                 gameMode == TwoMachinesPlay ||
10266                 gameMode == IcsPlayingWhite ||
10267                 gameMode == IcsPlayingBlack ||
10268                 gameMode == BeginningOfGame) {
10269                 char buf[MSG_SIZ];
10270                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10271                         resultDetails);
10272                 if (first.pr != NoProc) {
10273                     SendToProgram(buf, &first);
10274                 }
10275                 if (second.pr != NoProc &&
10276                     gameMode == TwoMachinesPlay) {
10277                     SendToProgram(buf, &second);
10278                 }
10279             }
10280         }
10281
10282         if (appData.icsActive) {
10283             if (appData.quietPlay &&
10284                 (gameMode == IcsPlayingWhite ||
10285                  gameMode == IcsPlayingBlack)) {
10286                 SendToICS(ics_prefix);
10287                 SendToICS("set shout 1\n");
10288             }
10289             nextGameMode = IcsIdle;
10290             ics_user_moved = FALSE;
10291             /* clean up premove.  It's ugly when the game has ended and the
10292              * premove highlights are still on the board.
10293              */
10294             if (gotPremove) {
10295               gotPremove = FALSE;
10296               ClearPremoveHighlights();
10297               DrawPosition(FALSE, boards[currentMove]);
10298             }
10299             if (whosays == GE_ICS) {
10300                 switch (result) {
10301                 case WhiteWins:
10302                     if (gameMode == IcsPlayingWhite)
10303                         PlayIcsWinSound();
10304                     else if(gameMode == IcsPlayingBlack)
10305                         PlayIcsLossSound();
10306                     break;
10307                 case BlackWins:
10308                     if (gameMode == IcsPlayingBlack)
10309                         PlayIcsWinSound();
10310                     else if(gameMode == IcsPlayingWhite)
10311                         PlayIcsLossSound();
10312                     break;
10313                 case GameIsDrawn:
10314                     PlayIcsDrawSound();
10315                     break;
10316                 default:
10317                     PlayIcsUnfinishedSound();
10318                 }
10319             }
10320         } else if (gameMode == EditGame ||
10321                    gameMode == PlayFromGameFile ||
10322                    gameMode == AnalyzeMode ||
10323                    gameMode == AnalyzeFile) {
10324             nextGameMode = gameMode;
10325         } else {
10326             nextGameMode = EndOfGame;
10327         }
10328         pausing = FALSE;
10329         ModeHighlight();
10330     } else {
10331         nextGameMode = gameMode;
10332     }
10333
10334     if (appData.noChessProgram) {
10335         gameMode = nextGameMode;
10336         ModeHighlight();
10337         endingGame = 0; /* [HGM] crash */
10338         return;
10339     }
10340
10341     if (first.reuse) {
10342         /* Put first chess program into idle state */
10343         if (first.pr != NoProc &&
10344             (gameMode == MachinePlaysWhite ||
10345              gameMode == MachinePlaysBlack ||
10346              gameMode == TwoMachinesPlay ||
10347              gameMode == IcsPlayingWhite ||
10348              gameMode == IcsPlayingBlack ||
10349              gameMode == BeginningOfGame)) {
10350             SendToProgram("force\n", &first);
10351             if (first.usePing) {
10352               char buf[MSG_SIZ];
10353               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10354               SendToProgram(buf, &first);
10355             }
10356         }
10357     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10358         /* Kill off first chess program */
10359         if (first.isr != NULL)
10360           RemoveInputSource(first.isr);
10361         first.isr = NULL;
10362
10363         if (first.pr != NoProc) {
10364             ExitAnalyzeMode();
10365             DoSleep( appData.delayBeforeQuit );
10366             SendToProgram("quit\n", &first);
10367             DoSleep( appData.delayAfterQuit );
10368             DestroyChildProcess(first.pr, first.useSigterm);
10369         }
10370         first.pr = NoProc;
10371     }
10372     if (second.reuse) {
10373         /* Put second chess program into idle state */
10374         if (second.pr != NoProc &&
10375             gameMode == TwoMachinesPlay) {
10376             SendToProgram("force\n", &second);
10377             if (second.usePing) {
10378               char buf[MSG_SIZ];
10379               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10380               SendToProgram(buf, &second);
10381             }
10382         }
10383     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10384         /* Kill off second chess program */
10385         if (second.isr != NULL)
10386           RemoveInputSource(second.isr);
10387         second.isr = NULL;
10388
10389         if (second.pr != NoProc) {
10390             DoSleep( appData.delayBeforeQuit );
10391             SendToProgram("quit\n", &second);
10392             DoSleep( appData.delayAfterQuit );
10393             DestroyChildProcess(second.pr, second.useSigterm);
10394         }
10395         second.pr = NoProc;
10396     }
10397
10398     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10399         char resChar = '=';
10400         switch (result) {
10401         case WhiteWins:
10402           resChar = '+';
10403           if (first.twoMachinesColor[0] == 'w') {
10404             first.matchWins++;
10405           } else {
10406             second.matchWins++;
10407           }
10408           break;
10409         case BlackWins:
10410           resChar = '-';
10411           if (first.twoMachinesColor[0] == 'b') {
10412             first.matchWins++;
10413           } else {
10414             second.matchWins++;
10415           }
10416           break;
10417         case GameUnfinished:
10418           resChar = ' ';
10419         default:
10420           break;
10421         }
10422
10423         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10424         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10425             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10426             ReserveGame(nextGame, resChar); // sets nextGame
10427             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10428             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10429         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10430
10431         if (nextGame <= appData.matchGames && !abortMatch) {
10432             gameMode = nextGameMode;
10433             matchGame = nextGame; // this will be overruled in tourney mode!
10434             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10435             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10436             endingGame = 0; /* [HGM] crash */
10437             return;
10438         } else {
10439             gameMode = nextGameMode;
10440             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10441                      first.tidy, second.tidy,
10442                      first.matchWins, second.matchWins,
10443                      appData.matchGames - (first.matchWins + second.matchWins));
10444             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10445             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10446             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10447             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10448                 first.twoMachinesColor = "black\n";
10449                 second.twoMachinesColor = "white\n";
10450             } else {
10451                 first.twoMachinesColor = "white\n";
10452                 second.twoMachinesColor = "black\n";
10453             }
10454         }
10455     }
10456     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10457         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10458       ExitAnalyzeMode();
10459     gameMode = nextGameMode;
10460     ModeHighlight();
10461     endingGame = 0;  /* [HGM] crash */
10462     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10463         if(matchMode == TRUE) { // match through command line: exit with or without popup
10464             if(ranking) {
10465                 ToNrEvent(forwardMostMove);
10466                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10467                 else ExitEvent(0);
10468             } else DisplayFatalError(buf, 0, 0);
10469         } else { // match through menu; just stop, with or without popup
10470             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10471             ModeHighlight();
10472             if(ranking){
10473                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10474             } else DisplayNote(buf);
10475       }
10476       if(ranking) free(ranking);
10477     }
10478 }
10479
10480 /* Assumes program was just initialized (initString sent).
10481    Leaves program in force mode. */
10482 void
10483 FeedMovesToProgram (ChessProgramState *cps, int upto)
10484 {
10485     int i;
10486
10487     if (appData.debugMode)
10488       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10489               startedFromSetupPosition ? "position and " : "",
10490               backwardMostMove, upto, cps->which);
10491     if(currentlyInitializedVariant != gameInfo.variant) {
10492       char buf[MSG_SIZ];
10493         // [HGM] variantswitch: make engine aware of new variant
10494         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10495                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10496         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10497         SendToProgram(buf, cps);
10498         currentlyInitializedVariant = gameInfo.variant;
10499     }
10500     SendToProgram("force\n", cps);
10501     if (startedFromSetupPosition) {
10502         SendBoard(cps, backwardMostMove);
10503     if (appData.debugMode) {
10504         fprintf(debugFP, "feedMoves\n");
10505     }
10506     }
10507     for (i = backwardMostMove; i < upto; i++) {
10508         SendMoveToProgram(i, cps);
10509     }
10510 }
10511
10512
10513 int
10514 ResurrectChessProgram ()
10515 {
10516      /* The chess program may have exited.
10517         If so, restart it and feed it all the moves made so far. */
10518     static int doInit = 0;
10519
10520     if (appData.noChessProgram) return 1;
10521
10522     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10523         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10524         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10525         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10526     } else {
10527         if (first.pr != NoProc) return 1;
10528         StartChessProgram(&first);
10529     }
10530     InitChessProgram(&first, FALSE);
10531     FeedMovesToProgram(&first, currentMove);
10532
10533     if (!first.sendTime) {
10534         /* can't tell gnuchess what its clock should read,
10535            so we bow to its notion. */
10536         ResetClocks();
10537         timeRemaining[0][currentMove] = whiteTimeRemaining;
10538         timeRemaining[1][currentMove] = blackTimeRemaining;
10539     }
10540
10541     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10542                 appData.icsEngineAnalyze) && first.analysisSupport) {
10543       SendToProgram("analyze\n", &first);
10544       first.analyzing = TRUE;
10545     }
10546     return 1;
10547 }
10548
10549 /*
10550  * Button procedures
10551  */
10552 void
10553 Reset (int redraw, int init)
10554 {
10555     int i;
10556
10557     if (appData.debugMode) {
10558         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10559                 redraw, init, gameMode);
10560     }
10561     CleanupTail(); // [HGM] vari: delete any stored variations
10562     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10563     pausing = pauseExamInvalid = FALSE;
10564     startedFromSetupPosition = blackPlaysFirst = FALSE;
10565     firstMove = TRUE;
10566     whiteFlag = blackFlag = FALSE;
10567     userOfferedDraw = FALSE;
10568     hintRequested = bookRequested = FALSE;
10569     first.maybeThinking = FALSE;
10570     second.maybeThinking = FALSE;
10571     first.bookSuspend = FALSE; // [HGM] book
10572     second.bookSuspend = FALSE;
10573     thinkOutput[0] = NULLCHAR;
10574     lastHint[0] = NULLCHAR;
10575     ClearGameInfo(&gameInfo);
10576     gameInfo.variant = StringToVariant(appData.variant);
10577     ics_user_moved = ics_clock_paused = FALSE;
10578     ics_getting_history = H_FALSE;
10579     ics_gamenum = -1;
10580     white_holding[0] = black_holding[0] = NULLCHAR;
10581     ClearProgramStats();
10582     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10583
10584     ResetFrontEnd();
10585     ClearHighlights();
10586     flipView = appData.flipView;
10587     ClearPremoveHighlights();
10588     gotPremove = FALSE;
10589     alarmSounded = FALSE;
10590
10591     GameEnds(EndOfFile, NULL, GE_PLAYER);
10592     if(appData.serverMovesName != NULL) {
10593         /* [HGM] prepare to make moves file for broadcasting */
10594         clock_t t = clock();
10595         if(serverMoves != NULL) fclose(serverMoves);
10596         serverMoves = fopen(appData.serverMovesName, "r");
10597         if(serverMoves != NULL) {
10598             fclose(serverMoves);
10599             /* delay 15 sec before overwriting, so all clients can see end */
10600             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10601         }
10602         serverMoves = fopen(appData.serverMovesName, "w");
10603     }
10604
10605     ExitAnalyzeMode();
10606     gameMode = BeginningOfGame;
10607     ModeHighlight();
10608     if(appData.icsActive) gameInfo.variant = VariantNormal;
10609     currentMove = forwardMostMove = backwardMostMove = 0;
10610     MarkTargetSquares(1);
10611     InitPosition(redraw);
10612     for (i = 0; i < MAX_MOVES; i++) {
10613         if (commentList[i] != NULL) {
10614             free(commentList[i]);
10615             commentList[i] = NULL;
10616         }
10617     }
10618     ResetClocks();
10619     timeRemaining[0][0] = whiteTimeRemaining;
10620     timeRemaining[1][0] = blackTimeRemaining;
10621
10622     if (first.pr == NoProc) {
10623         StartChessProgram(&first);
10624     }
10625     if (init) {
10626             InitChessProgram(&first, startedFromSetupPosition);
10627     }
10628     DisplayTitle("");
10629     DisplayMessage("", "");
10630     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10631     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10632 }
10633
10634 void
10635 AutoPlayGameLoop ()
10636 {
10637     for (;;) {
10638         if (!AutoPlayOneMove())
10639           return;
10640         if (matchMode || appData.timeDelay == 0)
10641           continue;
10642         if (appData.timeDelay < 0)
10643           return;
10644         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10645         break;
10646     }
10647 }
10648
10649
10650 int
10651 AutoPlayOneMove ()
10652 {
10653     int fromX, fromY, toX, toY;
10654
10655     if (appData.debugMode) {
10656       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10657     }
10658
10659     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10660       return FALSE;
10661
10662     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10663       pvInfoList[currentMove].depth = programStats.depth;
10664       pvInfoList[currentMove].score = programStats.score;
10665       pvInfoList[currentMove].time  = 0;
10666       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10667     }
10668
10669     if (currentMove >= forwardMostMove) {
10670       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10671 //      gameMode = EndOfGame;
10672 //      ModeHighlight();
10673
10674       /* [AS] Clear current move marker at the end of a game */
10675       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10676
10677       return FALSE;
10678     }
10679
10680     toX = moveList[currentMove][2] - AAA;
10681     toY = moveList[currentMove][3] - ONE;
10682
10683     if (moveList[currentMove][1] == '@') {
10684         if (appData.highlightLastMove) {
10685             SetHighlights(-1, -1, toX, toY);
10686         }
10687     } else {
10688         fromX = moveList[currentMove][0] - AAA;
10689         fromY = moveList[currentMove][1] - ONE;
10690
10691         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10692
10693         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10694
10695         if (appData.highlightLastMove) {
10696             SetHighlights(fromX, fromY, toX, toY);
10697         }
10698     }
10699     DisplayMove(currentMove);
10700     SendMoveToProgram(currentMove++, &first);
10701     DisplayBothClocks();
10702     DrawPosition(FALSE, boards[currentMove]);
10703     // [HGM] PV info: always display, routine tests if empty
10704     DisplayComment(currentMove - 1, commentList[currentMove]);
10705     return TRUE;
10706 }
10707
10708
10709 int
10710 LoadGameOneMove (ChessMove readAhead)
10711 {
10712     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10713     char promoChar = NULLCHAR;
10714     ChessMove moveType;
10715     char move[MSG_SIZ];
10716     char *p, *q;
10717
10718     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10719         gameMode != AnalyzeMode && gameMode != Training) {
10720         gameFileFP = NULL;
10721         return FALSE;
10722     }
10723
10724     yyboardindex = forwardMostMove;
10725     if (readAhead != EndOfFile) {
10726       moveType = readAhead;
10727     } else {
10728       if (gameFileFP == NULL)
10729           return FALSE;
10730       moveType = (ChessMove) Myylex();
10731     }
10732
10733     done = FALSE;
10734     switch (moveType) {
10735       case Comment:
10736         if (appData.debugMode)
10737           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10738         p = yy_text;
10739
10740         /* append the comment but don't display it */
10741         AppendComment(currentMove, p, FALSE);
10742         return TRUE;
10743
10744       case WhiteCapturesEnPassant:
10745       case BlackCapturesEnPassant:
10746       case WhitePromotion:
10747       case BlackPromotion:
10748       case WhiteNonPromotion:
10749       case BlackNonPromotion:
10750       case NormalMove:
10751       case WhiteKingSideCastle:
10752       case WhiteQueenSideCastle:
10753       case BlackKingSideCastle:
10754       case BlackQueenSideCastle:
10755       case WhiteKingSideCastleWild:
10756       case WhiteQueenSideCastleWild:
10757       case BlackKingSideCastleWild:
10758       case BlackQueenSideCastleWild:
10759       /* PUSH Fabien */
10760       case WhiteHSideCastleFR:
10761       case WhiteASideCastleFR:
10762       case BlackHSideCastleFR:
10763       case BlackASideCastleFR:
10764       /* POP Fabien */
10765         if (appData.debugMode)
10766           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10767         fromX = currentMoveString[0] - AAA;
10768         fromY = currentMoveString[1] - ONE;
10769         toX = currentMoveString[2] - AAA;
10770         toY = currentMoveString[3] - ONE;
10771         promoChar = currentMoveString[4];
10772         break;
10773
10774       case WhiteDrop:
10775       case BlackDrop:
10776         if (appData.debugMode)
10777           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10778         fromX = moveType == WhiteDrop ?
10779           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10780         (int) CharToPiece(ToLower(currentMoveString[0]));
10781         fromY = DROP_RANK;
10782         toX = currentMoveString[2] - AAA;
10783         toY = currentMoveString[3] - ONE;
10784         break;
10785
10786       case WhiteWins:
10787       case BlackWins:
10788       case GameIsDrawn:
10789       case GameUnfinished:
10790         if (appData.debugMode)
10791           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10792         p = strchr(yy_text, '{');
10793         if (p == NULL) p = strchr(yy_text, '(');
10794         if (p == NULL) {
10795             p = yy_text;
10796             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10797         } else {
10798             q = strchr(p, *p == '{' ? '}' : ')');
10799             if (q != NULL) *q = NULLCHAR;
10800             p++;
10801         }
10802         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10803         GameEnds(moveType, p, GE_FILE);
10804         done = TRUE;
10805         if (cmailMsgLoaded) {
10806             ClearHighlights();
10807             flipView = WhiteOnMove(currentMove);
10808             if (moveType == GameUnfinished) flipView = !flipView;
10809             if (appData.debugMode)
10810               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10811         }
10812         break;
10813
10814       case EndOfFile:
10815         if (appData.debugMode)
10816           fprintf(debugFP, "Parser hit end of file\n");
10817         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10818           case MT_NONE:
10819           case MT_CHECK:
10820             break;
10821           case MT_CHECKMATE:
10822           case MT_STAINMATE:
10823             if (WhiteOnMove(currentMove)) {
10824                 GameEnds(BlackWins, "Black mates", GE_FILE);
10825             } else {
10826                 GameEnds(WhiteWins, "White mates", GE_FILE);
10827             }
10828             break;
10829           case MT_STALEMATE:
10830             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10831             break;
10832         }
10833         done = TRUE;
10834         break;
10835
10836       case MoveNumberOne:
10837         if (lastLoadGameStart == GNUChessGame) {
10838             /* GNUChessGames have numbers, but they aren't move numbers */
10839             if (appData.debugMode)
10840               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10841                       yy_text, (int) moveType);
10842             return LoadGameOneMove(EndOfFile); /* tail recursion */
10843         }
10844         /* else fall thru */
10845
10846       case XBoardGame:
10847       case GNUChessGame:
10848       case PGNTag:
10849         /* Reached start of next game in file */
10850         if (appData.debugMode)
10851           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10852         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10853           case MT_NONE:
10854           case MT_CHECK:
10855             break;
10856           case MT_CHECKMATE:
10857           case MT_STAINMATE:
10858             if (WhiteOnMove(currentMove)) {
10859                 GameEnds(BlackWins, "Black mates", GE_FILE);
10860             } else {
10861                 GameEnds(WhiteWins, "White mates", GE_FILE);
10862             }
10863             break;
10864           case MT_STALEMATE:
10865             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10866             break;
10867         }
10868         done = TRUE;
10869         break;
10870
10871       case PositionDiagram:     /* should not happen; ignore */
10872       case ElapsedTime:         /* ignore */
10873       case NAG:                 /* ignore */
10874         if (appData.debugMode)
10875           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10876                   yy_text, (int) moveType);
10877         return LoadGameOneMove(EndOfFile); /* tail recursion */
10878
10879       case IllegalMove:
10880         if (appData.testLegality) {
10881             if (appData.debugMode)
10882               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10883             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10884                     (forwardMostMove / 2) + 1,
10885                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10886             DisplayError(move, 0);
10887             done = TRUE;
10888         } else {
10889             if (appData.debugMode)
10890               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10891                       yy_text, currentMoveString);
10892             fromX = currentMoveString[0] - AAA;
10893             fromY = currentMoveString[1] - ONE;
10894             toX = currentMoveString[2] - AAA;
10895             toY = currentMoveString[3] - ONE;
10896             promoChar = currentMoveString[4];
10897         }
10898         break;
10899
10900       case AmbiguousMove:
10901         if (appData.debugMode)
10902           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10903         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10904                 (forwardMostMove / 2) + 1,
10905                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10906         DisplayError(move, 0);
10907         done = TRUE;
10908         break;
10909
10910       default:
10911       case ImpossibleMove:
10912         if (appData.debugMode)
10913           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10914         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10915                 (forwardMostMove / 2) + 1,
10916                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10917         DisplayError(move, 0);
10918         done = TRUE;
10919         break;
10920     }
10921
10922     if (done) {
10923         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10924             DrawPosition(FALSE, boards[currentMove]);
10925             DisplayBothClocks();
10926             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10927               DisplayComment(currentMove - 1, commentList[currentMove]);
10928         }
10929         (void) StopLoadGameTimer();
10930         gameFileFP = NULL;
10931         cmailOldMove = forwardMostMove;
10932         return FALSE;
10933     } else {
10934         /* currentMoveString is set as a side-effect of yylex */
10935
10936         thinkOutput[0] = NULLCHAR;
10937         MakeMove(fromX, fromY, toX, toY, promoChar);
10938         currentMove = forwardMostMove;
10939         return TRUE;
10940     }
10941 }
10942
10943 /* Load the nth game from the given file */
10944 int
10945 LoadGameFromFile (char *filename, int n, char *title, int useList)
10946 {
10947     FILE *f;
10948     char buf[MSG_SIZ];
10949
10950     if (strcmp(filename, "-") == 0) {
10951         f = stdin;
10952         title = "stdin";
10953     } else {
10954         f = fopen(filename, "rb");
10955         if (f == NULL) {
10956           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10957             DisplayError(buf, errno);
10958             return FALSE;
10959         }
10960     }
10961     if (fseek(f, 0, 0) == -1) {
10962         /* f is not seekable; probably a pipe */
10963         useList = FALSE;
10964     }
10965     if (useList && n == 0) {
10966         int error = GameListBuild(f);
10967         if (error) {
10968             DisplayError(_("Cannot build game list"), error);
10969         } else if (!ListEmpty(&gameList) &&
10970                    ((ListGame *) gameList.tailPred)->number > 1) {
10971             GameListPopUp(f, title);
10972             return TRUE;
10973         }
10974         GameListDestroy();
10975         n = 1;
10976     }
10977     if (n == 0) n = 1;
10978     return LoadGame(f, n, title, FALSE);
10979 }
10980
10981
10982 void
10983 MakeRegisteredMove ()
10984 {
10985     int fromX, fromY, toX, toY;
10986     char promoChar;
10987     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10988         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10989           case CMAIL_MOVE:
10990           case CMAIL_DRAW:
10991             if (appData.debugMode)
10992               fprintf(debugFP, "Restoring %s for game %d\n",
10993                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10994
10995             thinkOutput[0] = NULLCHAR;
10996             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10997             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10998             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10999             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11000             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11001             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11002             MakeMove(fromX, fromY, toX, toY, promoChar);
11003             ShowMove(fromX, fromY, toX, toY);
11004
11005             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11006               case MT_NONE:
11007               case MT_CHECK:
11008                 break;
11009
11010               case MT_CHECKMATE:
11011               case MT_STAINMATE:
11012                 if (WhiteOnMove(currentMove)) {
11013                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11014                 } else {
11015                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11016                 }
11017                 break;
11018
11019               case MT_STALEMATE:
11020                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11021                 break;
11022             }
11023
11024             break;
11025
11026           case CMAIL_RESIGN:
11027             if (WhiteOnMove(currentMove)) {
11028                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11029             } else {
11030                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11031             }
11032             break;
11033
11034           case CMAIL_ACCEPT:
11035             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11036             break;
11037
11038           default:
11039             break;
11040         }
11041     }
11042
11043     return;
11044 }
11045
11046 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11047 int
11048 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11049 {
11050     int retVal;
11051
11052     if (gameNumber > nCmailGames) {
11053         DisplayError(_("No more games in this message"), 0);
11054         return FALSE;
11055     }
11056     if (f == lastLoadGameFP) {
11057         int offset = gameNumber - lastLoadGameNumber;
11058         if (offset == 0) {
11059             cmailMsg[0] = NULLCHAR;
11060             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11061                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11062                 nCmailMovesRegistered--;
11063             }
11064             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11065             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11066                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11067             }
11068         } else {
11069             if (! RegisterMove()) return FALSE;
11070         }
11071     }
11072
11073     retVal = LoadGame(f, gameNumber, title, useList);
11074
11075     /* Make move registered during previous look at this game, if any */
11076     MakeRegisteredMove();
11077
11078     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11079         commentList[currentMove]
11080           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11081         DisplayComment(currentMove - 1, commentList[currentMove]);
11082     }
11083
11084     return retVal;
11085 }
11086
11087 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11088 int
11089 ReloadGame (int offset)
11090 {
11091     int gameNumber = lastLoadGameNumber + offset;
11092     if (lastLoadGameFP == NULL) {
11093         DisplayError(_("No game has been loaded yet"), 0);
11094         return FALSE;
11095     }
11096     if (gameNumber <= 0) {
11097         DisplayError(_("Can't back up any further"), 0);
11098         return FALSE;
11099     }
11100     if (cmailMsgLoaded) {
11101         return CmailLoadGame(lastLoadGameFP, gameNumber,
11102                              lastLoadGameTitle, lastLoadGameUseList);
11103     } else {
11104         return LoadGame(lastLoadGameFP, gameNumber,
11105                         lastLoadGameTitle, lastLoadGameUseList);
11106     }
11107 }
11108
11109 int keys[EmptySquare+1];
11110
11111 int
11112 PositionMatches (Board b1, Board b2)
11113 {
11114     int r, f, sum=0;
11115     switch(appData.searchMode) {
11116         case 1: return CompareWithRights(b1, b2);
11117         case 2:
11118             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11119                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11120             }
11121             return TRUE;
11122         case 3:
11123             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11124               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11125                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11126             }
11127             return sum==0;
11128         case 4:
11129             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11130                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11131             }
11132             return sum==0;
11133     }
11134     return TRUE;
11135 }
11136
11137 #define Q_PROMO  4
11138 #define Q_EP     3
11139 #define Q_BCASTL 2
11140 #define Q_WCASTL 1
11141
11142 int pieceList[256], quickBoard[256];
11143 ChessSquare pieceType[256] = { EmptySquare };
11144 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11145 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11146 int soughtTotal, turn;
11147 Boolean epOK, flipSearch;
11148
11149 typedef struct {
11150     unsigned char piece, to;
11151 } Move;
11152
11153 #define DSIZE (250000)
11154
11155 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11156 Move *moveDatabase = initialSpace;
11157 unsigned int movePtr, dataSize = DSIZE;
11158
11159 int
11160 MakePieceList (Board board, int *counts)
11161 {
11162     int r, f, n=Q_PROMO, total=0;
11163     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11164     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11165         int sq = f + (r<<4);
11166         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11167             quickBoard[sq] = ++n;
11168             pieceList[n] = sq;
11169             pieceType[n] = board[r][f];
11170             counts[board[r][f]]++;
11171             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11172             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11173             total++;
11174         }
11175     }
11176     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11177     return total;
11178 }
11179
11180 void
11181 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11182 {
11183     int sq = fromX + (fromY<<4);
11184     int piece = quickBoard[sq];
11185     quickBoard[sq] = 0;
11186     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11187     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11188         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11189         moveDatabase[movePtr++].piece = Q_WCASTL;
11190         quickBoard[sq] = piece;
11191         piece = quickBoard[from]; quickBoard[from] = 0;
11192         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11193     } else
11194     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11195         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11196         moveDatabase[movePtr++].piece = Q_BCASTL;
11197         quickBoard[sq] = piece;
11198         piece = quickBoard[from]; quickBoard[from] = 0;
11199         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11200     } else
11201     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11202         quickBoard[(fromY<<4)+toX] = 0;
11203         moveDatabase[movePtr].piece = Q_EP;
11204         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11205         moveDatabase[movePtr].to = sq;
11206     } else
11207     if(promoPiece != pieceType[piece]) {
11208         moveDatabase[movePtr++].piece = Q_PROMO;
11209         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11210     }
11211     moveDatabase[movePtr].piece = piece;
11212     quickBoard[sq] = piece;
11213     movePtr++;
11214 }
11215
11216 int
11217 PackGame (Board board)
11218 {
11219     Move *newSpace = NULL;
11220     moveDatabase[movePtr].piece = 0; // terminate previous game
11221     if(movePtr > dataSize) {
11222         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11223         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11224         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11225         if(newSpace) {
11226             int i;
11227             Move *p = moveDatabase, *q = newSpace;
11228             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11229             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11230             moveDatabase = newSpace;
11231         } else { // calloc failed, we must be out of memory. Too bad...
11232             dataSize = 0; // prevent calloc events for all subsequent games
11233             return 0;     // and signal this one isn't cached
11234         }
11235     }
11236     movePtr++;
11237     MakePieceList(board, counts);
11238     return movePtr;
11239 }
11240
11241 int
11242 QuickCompare (Board board, int *minCounts, int *maxCounts)
11243 {   // compare according to search mode
11244     int r, f;
11245     switch(appData.searchMode)
11246     {
11247       case 1: // exact position match
11248         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11249         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11250             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11251         }
11252         break;
11253       case 2: // can have extra material on empty squares
11254         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11255             if(board[r][f] == EmptySquare) continue;
11256             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11257         }
11258         break;
11259       case 3: // material with exact Pawn structure
11260         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11261             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11262             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11263         } // fall through to material comparison
11264       case 4: // exact material
11265         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11266         break;
11267       case 6: // material range with given imbalance
11268         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11269         // fall through to range comparison
11270       case 5: // material range
11271         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11272     }
11273     return TRUE;
11274 }
11275
11276 int
11277 QuickScan (Board board, Move *move)
11278 {   // reconstruct game,and compare all positions in it
11279     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11280     do {
11281         int piece = move->piece;
11282         int to = move->to, from = pieceList[piece];
11283         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11284           if(!piece) return -1;
11285           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11286             piece = (++move)->piece;
11287             from = pieceList[piece];
11288             counts[pieceType[piece]]--;
11289             pieceType[piece] = (ChessSquare) move->to;
11290             counts[move->to]++;
11291           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11292             counts[pieceType[quickBoard[to]]]--;
11293             quickBoard[to] = 0; total--;
11294             move++;
11295             continue;
11296           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11297             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11298             from  = pieceList[piece]; // so this must be King
11299             quickBoard[from] = 0;
11300             quickBoard[to] = piece;
11301             pieceList[piece] = to;
11302             move++;
11303             continue;
11304           }
11305         }
11306         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11307         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11308         quickBoard[from] = 0;
11309         quickBoard[to] = piece;
11310         pieceList[piece] = to;
11311         cnt++; turn ^= 3;
11312         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11313            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11314            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11315                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11316           ) {
11317             static int lastCounts[EmptySquare+1];
11318             int i;
11319             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11320             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11321         } else stretch = 0;
11322         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11323         move++;
11324     } while(1);
11325 }
11326
11327 void
11328 InitSearch ()
11329 {
11330     int r, f;
11331     flipSearch = FALSE;
11332     CopyBoard(soughtBoard, boards[currentMove]);
11333     soughtTotal = MakePieceList(soughtBoard, maxSought);
11334     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11335     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11336     CopyBoard(reverseBoard, boards[currentMove]);
11337     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11338         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11339         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11340         reverseBoard[r][f] = piece;
11341     }
11342     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11343     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11344     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11345                  || (boards[currentMove][CASTLING][2] == NoRights || 
11346                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11347                  && (boards[currentMove][CASTLING][5] == NoRights || 
11348                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11349       ) {
11350         flipSearch = TRUE;
11351         CopyBoard(flipBoard, soughtBoard);
11352         CopyBoard(rotateBoard, reverseBoard);
11353         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11354             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11355             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11356         }
11357     }
11358     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11359     if(appData.searchMode >= 5) {
11360         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11361         MakePieceList(soughtBoard, minSought);
11362         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11363     }
11364     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11365         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11366 }
11367
11368 GameInfo dummyInfo;
11369
11370 int
11371 GameContainsPosition (FILE *f, ListGame *lg)
11372 {
11373     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11374     int fromX, fromY, toX, toY;
11375     char promoChar;
11376     static int initDone=FALSE;
11377
11378     // weed out games based on numerical tag comparison
11379     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11380     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11381     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11382     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11383     if(!initDone) {
11384         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11385         initDone = TRUE;
11386     }
11387     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11388     else CopyBoard(boards[scratch], initialPosition); // default start position
11389     if(lg->moves) {
11390         turn = btm + 1;
11391         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11392         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11393     }
11394     if(btm) plyNr++;
11395     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11396     fseek(f, lg->offset, 0);
11397     yynewfile(f);
11398     while(1) {
11399         yyboardindex = scratch;
11400         quickFlag = plyNr+1;
11401         next = Myylex();
11402         quickFlag = 0;
11403         switch(next) {
11404             case PGNTag:
11405                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11406             default:
11407                 continue;
11408
11409             case XBoardGame:
11410             case GNUChessGame:
11411                 if(plyNr) return -1; // after we have seen moves, this is for new game
11412               continue;
11413
11414             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11415             case ImpossibleMove:
11416             case WhiteWins: // game ends here with these four
11417             case BlackWins:
11418             case GameIsDrawn:
11419             case GameUnfinished:
11420                 return -1;
11421
11422             case IllegalMove:
11423                 if(appData.testLegality) return -1;
11424             case WhiteCapturesEnPassant:
11425             case BlackCapturesEnPassant:
11426             case WhitePromotion:
11427             case BlackPromotion:
11428             case WhiteNonPromotion:
11429             case BlackNonPromotion:
11430             case NormalMove:
11431             case WhiteKingSideCastle:
11432             case WhiteQueenSideCastle:
11433             case BlackKingSideCastle:
11434             case BlackQueenSideCastle:
11435             case WhiteKingSideCastleWild:
11436             case WhiteQueenSideCastleWild:
11437             case BlackKingSideCastleWild:
11438             case BlackQueenSideCastleWild:
11439             case WhiteHSideCastleFR:
11440             case WhiteASideCastleFR:
11441             case BlackHSideCastleFR:
11442             case BlackASideCastleFR:
11443                 fromX = currentMoveString[0] - AAA;
11444                 fromY = currentMoveString[1] - ONE;
11445                 toX = currentMoveString[2] - AAA;
11446                 toY = currentMoveString[3] - ONE;
11447                 promoChar = currentMoveString[4];
11448                 break;
11449             case WhiteDrop:
11450             case BlackDrop:
11451                 fromX = next == WhiteDrop ?
11452                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11453                   (int) CharToPiece(ToLower(currentMoveString[0]));
11454                 fromY = DROP_RANK;
11455                 toX = currentMoveString[2] - AAA;
11456                 toY = currentMoveString[3] - ONE;
11457                 promoChar = 0;
11458                 break;
11459         }
11460         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11461         plyNr++;
11462         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11463         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11464         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11465         if(appData.findMirror) {
11466             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11467             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11468         }
11469     }
11470 }
11471
11472 /* Load the nth game from open file f */
11473 int
11474 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11475 {
11476     ChessMove cm;
11477     char buf[MSG_SIZ];
11478     int gn = gameNumber;
11479     ListGame *lg = NULL;
11480     int numPGNTags = 0;
11481     int err, pos = -1;
11482     GameMode oldGameMode;
11483     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11484
11485     if (appData.debugMode)
11486         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11487
11488     if (gameMode == Training )
11489         SetTrainingModeOff();
11490
11491     oldGameMode = gameMode;
11492     if (gameMode != BeginningOfGame) {
11493       Reset(FALSE, TRUE);
11494     }
11495
11496     gameFileFP = f;
11497     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11498         fclose(lastLoadGameFP);
11499     }
11500
11501     if (useList) {
11502         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11503
11504         if (lg) {
11505             fseek(f, lg->offset, 0);
11506             GameListHighlight(gameNumber);
11507             pos = lg->position;
11508             gn = 1;
11509         }
11510         else {
11511             DisplayError(_("Game number out of range"), 0);
11512             return FALSE;
11513         }
11514     } else {
11515         GameListDestroy();
11516         if (fseek(f, 0, 0) == -1) {
11517             if (f == lastLoadGameFP ?
11518                 gameNumber == lastLoadGameNumber + 1 :
11519                 gameNumber == 1) {
11520                 gn = 1;
11521             } else {
11522                 DisplayError(_("Can't seek on game file"), 0);
11523                 return FALSE;
11524             }
11525         }
11526     }
11527     lastLoadGameFP = f;
11528     lastLoadGameNumber = gameNumber;
11529     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11530     lastLoadGameUseList = useList;
11531
11532     yynewfile(f);
11533
11534     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11535       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11536                 lg->gameInfo.black);
11537             DisplayTitle(buf);
11538     } else if (*title != NULLCHAR) {
11539         if (gameNumber > 1) {
11540           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11541             DisplayTitle(buf);
11542         } else {
11543             DisplayTitle(title);
11544         }
11545     }
11546
11547     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11548         gameMode = PlayFromGameFile;
11549         ModeHighlight();
11550     }
11551
11552     currentMove = forwardMostMove = backwardMostMove = 0;
11553     CopyBoard(boards[0], initialPosition);
11554     StopClocks();
11555
11556     /*
11557      * Skip the first gn-1 games in the file.
11558      * Also skip over anything that precedes an identifiable
11559      * start of game marker, to avoid being confused by
11560      * garbage at the start of the file.  Currently
11561      * recognized start of game markers are the move number "1",
11562      * the pattern "gnuchess .* game", the pattern
11563      * "^[#;%] [^ ]* game file", and a PGN tag block.
11564      * A game that starts with one of the latter two patterns
11565      * will also have a move number 1, possibly
11566      * following a position diagram.
11567      * 5-4-02: Let's try being more lenient and allowing a game to
11568      * start with an unnumbered move.  Does that break anything?
11569      */
11570     cm = lastLoadGameStart = EndOfFile;
11571     while (gn > 0) {
11572         yyboardindex = forwardMostMove;
11573         cm = (ChessMove) Myylex();
11574         switch (cm) {
11575           case EndOfFile:
11576             if (cmailMsgLoaded) {
11577                 nCmailGames = CMAIL_MAX_GAMES - gn;
11578             } else {
11579                 Reset(TRUE, TRUE);
11580                 DisplayError(_("Game not found in file"), 0);
11581             }
11582             return FALSE;
11583
11584           case GNUChessGame:
11585           case XBoardGame:
11586             gn--;
11587             lastLoadGameStart = cm;
11588             break;
11589
11590           case MoveNumberOne:
11591             switch (lastLoadGameStart) {
11592               case GNUChessGame:
11593               case XBoardGame:
11594               case PGNTag:
11595                 break;
11596               case MoveNumberOne:
11597               case EndOfFile:
11598                 gn--;           /* count this game */
11599                 lastLoadGameStart = cm;
11600                 break;
11601               default:
11602                 /* impossible */
11603                 break;
11604             }
11605             break;
11606
11607           case PGNTag:
11608             switch (lastLoadGameStart) {
11609               case GNUChessGame:
11610               case PGNTag:
11611               case MoveNumberOne:
11612               case EndOfFile:
11613                 gn--;           /* count this game */
11614                 lastLoadGameStart = cm;
11615                 break;
11616               case XBoardGame:
11617                 lastLoadGameStart = cm; /* game counted already */
11618                 break;
11619               default:
11620                 /* impossible */
11621                 break;
11622             }
11623             if (gn > 0) {
11624                 do {
11625                     yyboardindex = forwardMostMove;
11626                     cm = (ChessMove) Myylex();
11627                 } while (cm == PGNTag || cm == Comment);
11628             }
11629             break;
11630
11631           case WhiteWins:
11632           case BlackWins:
11633           case GameIsDrawn:
11634             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11635                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11636                     != CMAIL_OLD_RESULT) {
11637                     nCmailResults ++ ;
11638                     cmailResult[  CMAIL_MAX_GAMES
11639                                 - gn - 1] = CMAIL_OLD_RESULT;
11640                 }
11641             }
11642             break;
11643
11644           case NormalMove:
11645             /* Only a NormalMove can be at the start of a game
11646              * without a position diagram. */
11647             if (lastLoadGameStart == EndOfFile ) {
11648               gn--;
11649               lastLoadGameStart = MoveNumberOne;
11650             }
11651             break;
11652
11653           default:
11654             break;
11655         }
11656     }
11657
11658     if (appData.debugMode)
11659       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11660
11661     if (cm == XBoardGame) {
11662         /* Skip any header junk before position diagram and/or move 1 */
11663         for (;;) {
11664             yyboardindex = forwardMostMove;
11665             cm = (ChessMove) Myylex();
11666
11667             if (cm == EndOfFile ||
11668                 cm == GNUChessGame || cm == XBoardGame) {
11669                 /* Empty game; pretend end-of-file and handle later */
11670                 cm = EndOfFile;
11671                 break;
11672             }
11673
11674             if (cm == MoveNumberOne || cm == PositionDiagram ||
11675                 cm == PGNTag || cm == Comment)
11676               break;
11677         }
11678     } else if (cm == GNUChessGame) {
11679         if (gameInfo.event != NULL) {
11680             free(gameInfo.event);
11681         }
11682         gameInfo.event = StrSave(yy_text);
11683     }
11684
11685     startedFromSetupPosition = FALSE;
11686     while (cm == PGNTag) {
11687         if (appData.debugMode)
11688           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11689         err = ParsePGNTag(yy_text, &gameInfo);
11690         if (!err) numPGNTags++;
11691
11692         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11693         if(gameInfo.variant != oldVariant) {
11694             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11695             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11696             InitPosition(TRUE);
11697             oldVariant = gameInfo.variant;
11698             if (appData.debugMode)
11699               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11700         }
11701
11702
11703         if (gameInfo.fen != NULL) {
11704           Board initial_position;
11705           startedFromSetupPosition = TRUE;
11706           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11707             Reset(TRUE, TRUE);
11708             DisplayError(_("Bad FEN position in file"), 0);
11709             return FALSE;
11710           }
11711           CopyBoard(boards[0], initial_position);
11712           if (blackPlaysFirst) {
11713             currentMove = forwardMostMove = backwardMostMove = 1;
11714             CopyBoard(boards[1], initial_position);
11715             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11716             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11717             timeRemaining[0][1] = whiteTimeRemaining;
11718             timeRemaining[1][1] = blackTimeRemaining;
11719             if (commentList[0] != NULL) {
11720               commentList[1] = commentList[0];
11721               commentList[0] = NULL;
11722             }
11723           } else {
11724             currentMove = forwardMostMove = backwardMostMove = 0;
11725           }
11726           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11727           {   int i;
11728               initialRulePlies = FENrulePlies;
11729               for( i=0; i< nrCastlingRights; i++ )
11730                   initialRights[i] = initial_position[CASTLING][i];
11731           }
11732           yyboardindex = forwardMostMove;
11733           free(gameInfo.fen);
11734           gameInfo.fen = NULL;
11735         }
11736
11737         yyboardindex = forwardMostMove;
11738         cm = (ChessMove) Myylex();
11739
11740         /* Handle comments interspersed among the tags */
11741         while (cm == Comment) {
11742             char *p;
11743             if (appData.debugMode)
11744               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11745             p = yy_text;
11746             AppendComment(currentMove, p, FALSE);
11747             yyboardindex = forwardMostMove;
11748             cm = (ChessMove) Myylex();
11749         }
11750     }
11751
11752     /* don't rely on existence of Event tag since if game was
11753      * pasted from clipboard the Event tag may not exist
11754      */
11755     if (numPGNTags > 0){
11756         char *tags;
11757         if (gameInfo.variant == VariantNormal) {
11758           VariantClass v = StringToVariant(gameInfo.event);
11759           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11760           if(v < VariantShogi) gameInfo.variant = v;
11761         }
11762         if (!matchMode) {
11763           if( appData.autoDisplayTags ) {
11764             tags = PGNTags(&gameInfo);
11765             TagsPopUp(tags, CmailMsg());
11766             free(tags);
11767           }
11768         }
11769     } else {
11770         /* Make something up, but don't display it now */
11771         SetGameInfo();
11772         TagsPopDown();
11773     }
11774
11775     if (cm == PositionDiagram) {
11776         int i, j;
11777         char *p;
11778         Board initial_position;
11779
11780         if (appData.debugMode)
11781           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11782
11783         if (!startedFromSetupPosition) {
11784             p = yy_text;
11785             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11786               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11787                 switch (*p) {
11788                   case '{':
11789                   case '[':
11790                   case '-':
11791                   case ' ':
11792                   case '\t':
11793                   case '\n':
11794                   case '\r':
11795                     break;
11796                   default:
11797                     initial_position[i][j++] = CharToPiece(*p);
11798                     break;
11799                 }
11800             while (*p == ' ' || *p == '\t' ||
11801                    *p == '\n' || *p == '\r') p++;
11802
11803             if (strncmp(p, "black", strlen("black"))==0)
11804               blackPlaysFirst = TRUE;
11805             else
11806               blackPlaysFirst = FALSE;
11807             startedFromSetupPosition = TRUE;
11808
11809             CopyBoard(boards[0], initial_position);
11810             if (blackPlaysFirst) {
11811                 currentMove = forwardMostMove = backwardMostMove = 1;
11812                 CopyBoard(boards[1], initial_position);
11813                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11814                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11815                 timeRemaining[0][1] = whiteTimeRemaining;
11816                 timeRemaining[1][1] = blackTimeRemaining;
11817                 if (commentList[0] != NULL) {
11818                     commentList[1] = commentList[0];
11819                     commentList[0] = NULL;
11820                 }
11821             } else {
11822                 currentMove = forwardMostMove = backwardMostMove = 0;
11823             }
11824         }
11825         yyboardindex = forwardMostMove;
11826         cm = (ChessMove) Myylex();
11827     }
11828
11829     if (first.pr == NoProc) {
11830         StartChessProgram(&first);
11831     }
11832     InitChessProgram(&first, FALSE);
11833     SendToProgram("force\n", &first);
11834     if (startedFromSetupPosition) {
11835         SendBoard(&first, forwardMostMove);
11836     if (appData.debugMode) {
11837         fprintf(debugFP, "Load Game\n");
11838     }
11839         DisplayBothClocks();
11840     }
11841
11842     /* [HGM] server: flag to write setup moves in broadcast file as one */
11843     loadFlag = appData.suppressLoadMoves;
11844
11845     while (cm == Comment) {
11846         char *p;
11847         if (appData.debugMode)
11848           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11849         p = yy_text;
11850         AppendComment(currentMove, p, FALSE);
11851         yyboardindex = forwardMostMove;
11852         cm = (ChessMove) Myylex();
11853     }
11854
11855     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11856         cm == WhiteWins || cm == BlackWins ||
11857         cm == GameIsDrawn || cm == GameUnfinished) {
11858         DisplayMessage("", _("No moves in game"));
11859         if (cmailMsgLoaded) {
11860             if (appData.debugMode)
11861               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11862             ClearHighlights();
11863             flipView = FALSE;
11864         }
11865         DrawPosition(FALSE, boards[currentMove]);
11866         DisplayBothClocks();
11867         gameMode = EditGame;
11868         ModeHighlight();
11869         gameFileFP = NULL;
11870         cmailOldMove = 0;
11871         return TRUE;
11872     }
11873
11874     // [HGM] PV info: routine tests if comment empty
11875     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11876         DisplayComment(currentMove - 1, commentList[currentMove]);
11877     }
11878     if (!matchMode && appData.timeDelay != 0)
11879       DrawPosition(FALSE, boards[currentMove]);
11880
11881     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11882       programStats.ok_to_send = 1;
11883     }
11884
11885     /* if the first token after the PGN tags is a move
11886      * and not move number 1, retrieve it from the parser
11887      */
11888     if (cm != MoveNumberOne)
11889         LoadGameOneMove(cm);
11890
11891     /* load the remaining moves from the file */
11892     while (LoadGameOneMove(EndOfFile)) {
11893       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11894       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11895     }
11896
11897     /* rewind to the start of the game */
11898     currentMove = backwardMostMove;
11899
11900     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11901
11902     if (oldGameMode == AnalyzeFile ||
11903         oldGameMode == AnalyzeMode) {
11904       AnalyzeFileEvent();
11905     }
11906
11907     if (!matchMode && pos >= 0) {
11908         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11909     } else
11910     if (matchMode || appData.timeDelay == 0) {
11911       ToEndEvent();
11912     } else if (appData.timeDelay > 0) {
11913       AutoPlayGameLoop();
11914     }
11915
11916     if (appData.debugMode)
11917         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11918
11919     loadFlag = 0; /* [HGM] true game starts */
11920     return TRUE;
11921 }
11922
11923 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11924 int
11925 ReloadPosition (int offset)
11926 {
11927     int positionNumber = lastLoadPositionNumber + offset;
11928     if (lastLoadPositionFP == NULL) {
11929         DisplayError(_("No position has been loaded yet"), 0);
11930         return FALSE;
11931     }
11932     if (positionNumber <= 0) {
11933         DisplayError(_("Can't back up any further"), 0);
11934         return FALSE;
11935     }
11936     return LoadPosition(lastLoadPositionFP, positionNumber,
11937                         lastLoadPositionTitle);
11938 }
11939
11940 /* Load the nth position from the given file */
11941 int
11942 LoadPositionFromFile (char *filename, int n, char *title)
11943 {
11944     FILE *f;
11945     char buf[MSG_SIZ];
11946
11947     if (strcmp(filename, "-") == 0) {
11948         return LoadPosition(stdin, n, "stdin");
11949     } else {
11950         f = fopen(filename, "rb");
11951         if (f == NULL) {
11952             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11953             DisplayError(buf, errno);
11954             return FALSE;
11955         } else {
11956             return LoadPosition(f, n, title);
11957         }
11958     }
11959 }
11960
11961 /* Load the nth position from the given open file, and close it */
11962 int
11963 LoadPosition (FILE *f, int positionNumber, char *title)
11964 {
11965     char *p, line[MSG_SIZ];
11966     Board initial_position;
11967     int i, j, fenMode, pn;
11968
11969     if (gameMode == Training )
11970         SetTrainingModeOff();
11971
11972     if (gameMode != BeginningOfGame) {
11973         Reset(FALSE, TRUE);
11974     }
11975     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11976         fclose(lastLoadPositionFP);
11977     }
11978     if (positionNumber == 0) positionNumber = 1;
11979     lastLoadPositionFP = f;
11980     lastLoadPositionNumber = positionNumber;
11981     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11982     if (first.pr == NoProc && !appData.noChessProgram) {
11983       StartChessProgram(&first);
11984       InitChessProgram(&first, FALSE);
11985     }
11986     pn = positionNumber;
11987     if (positionNumber < 0) {
11988         /* Negative position number means to seek to that byte offset */
11989         if (fseek(f, -positionNumber, 0) == -1) {
11990             DisplayError(_("Can't seek on position file"), 0);
11991             return FALSE;
11992         };
11993         pn = 1;
11994     } else {
11995         if (fseek(f, 0, 0) == -1) {
11996             if (f == lastLoadPositionFP ?
11997                 positionNumber == lastLoadPositionNumber + 1 :
11998                 positionNumber == 1) {
11999                 pn = 1;
12000             } else {
12001                 DisplayError(_("Can't seek on position file"), 0);
12002                 return FALSE;
12003             }
12004         }
12005     }
12006     /* See if this file is FEN or old-style xboard */
12007     if (fgets(line, MSG_SIZ, f) == NULL) {
12008         DisplayError(_("Position not found in file"), 0);
12009         return FALSE;
12010     }
12011     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12012     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12013
12014     if (pn >= 2) {
12015         if (fenMode || line[0] == '#') pn--;
12016         while (pn > 0) {
12017             /* skip positions before number pn */
12018             if (fgets(line, MSG_SIZ, f) == NULL) {
12019                 Reset(TRUE, TRUE);
12020                 DisplayError(_("Position not found in file"), 0);
12021                 return FALSE;
12022             }
12023             if (fenMode || line[0] == '#') pn--;
12024         }
12025     }
12026
12027     if (fenMode) {
12028         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12029             DisplayError(_("Bad FEN position in file"), 0);
12030             return FALSE;
12031         }
12032     } else {
12033         (void) fgets(line, MSG_SIZ, f);
12034         (void) fgets(line, MSG_SIZ, f);
12035
12036         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12037             (void) fgets(line, MSG_SIZ, f);
12038             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12039                 if (*p == ' ')
12040                   continue;
12041                 initial_position[i][j++] = CharToPiece(*p);
12042             }
12043         }
12044
12045         blackPlaysFirst = FALSE;
12046         if (!feof(f)) {
12047             (void) fgets(line, MSG_SIZ, f);
12048             if (strncmp(line, "black", strlen("black"))==0)
12049               blackPlaysFirst = TRUE;
12050         }
12051     }
12052     startedFromSetupPosition = TRUE;
12053
12054     CopyBoard(boards[0], initial_position);
12055     if (blackPlaysFirst) {
12056         currentMove = forwardMostMove = backwardMostMove = 1;
12057         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12058         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12059         CopyBoard(boards[1], initial_position);
12060         DisplayMessage("", _("Black to play"));
12061     } else {
12062         currentMove = forwardMostMove = backwardMostMove = 0;
12063         DisplayMessage("", _("White to play"));
12064     }
12065     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12066     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12067         SendToProgram("force\n", &first);
12068         SendBoard(&first, forwardMostMove);
12069     }
12070     if (appData.debugMode) {
12071 int i, j;
12072   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12073   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12074         fprintf(debugFP, "Load Position\n");
12075     }
12076
12077     if (positionNumber > 1) {
12078       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12079         DisplayTitle(line);
12080     } else {
12081         DisplayTitle(title);
12082     }
12083     gameMode = EditGame;
12084     ModeHighlight();
12085     ResetClocks();
12086     timeRemaining[0][1] = whiteTimeRemaining;
12087     timeRemaining[1][1] = blackTimeRemaining;
12088     DrawPosition(FALSE, boards[currentMove]);
12089
12090     return TRUE;
12091 }
12092
12093
12094 void
12095 CopyPlayerNameIntoFileName (char **dest, char *src)
12096 {
12097     while (*src != NULLCHAR && *src != ',') {
12098         if (*src == ' ') {
12099             *(*dest)++ = '_';
12100             src++;
12101         } else {
12102             *(*dest)++ = *src++;
12103         }
12104     }
12105 }
12106
12107 char *
12108 DefaultFileName (char *ext)
12109 {
12110     static char def[MSG_SIZ];
12111     char *p;
12112
12113     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12114         p = def;
12115         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12116         *p++ = '-';
12117         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12118         *p++ = '.';
12119         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12120     } else {
12121         def[0] = NULLCHAR;
12122     }
12123     return def;
12124 }
12125
12126 /* Save the current game to the given file */
12127 int
12128 SaveGameToFile (char *filename, int append)
12129 {
12130     FILE *f;
12131     char buf[MSG_SIZ];
12132     int result, i, t,tot=0;
12133
12134     if (strcmp(filename, "-") == 0) {
12135         return SaveGame(stdout, 0, NULL);
12136     } else {
12137         for(i=0; i<10; i++) { // upto 10 tries
12138              f = fopen(filename, append ? "a" : "w");
12139              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12140              if(f || errno != 13) break;
12141              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12142              tot += t;
12143         }
12144         if (f == NULL) {
12145             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12146             DisplayError(buf, errno);
12147             return FALSE;
12148         } else {
12149             safeStrCpy(buf, lastMsg, MSG_SIZ);
12150             DisplayMessage(_("Waiting for access to save file"), "");
12151             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12152             DisplayMessage(_("Saving game"), "");
12153             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12154             result = SaveGame(f, 0, NULL);
12155             DisplayMessage(buf, "");
12156             return result;
12157         }
12158     }
12159 }
12160
12161 char *
12162 SavePart (char *str)
12163 {
12164     static char buf[MSG_SIZ];
12165     char *p;
12166
12167     p = strchr(str, ' ');
12168     if (p == NULL) return str;
12169     strncpy(buf, str, p - str);
12170     buf[p - str] = NULLCHAR;
12171     return buf;
12172 }
12173
12174 #define PGN_MAX_LINE 75
12175
12176 #define PGN_SIDE_WHITE  0
12177 #define PGN_SIDE_BLACK  1
12178
12179 static int
12180 FindFirstMoveOutOfBook (int side)
12181 {
12182     int result = -1;
12183
12184     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12185         int index = backwardMostMove;
12186         int has_book_hit = 0;
12187
12188         if( (index % 2) != side ) {
12189             index++;
12190         }
12191
12192         while( index < forwardMostMove ) {
12193             /* Check to see if engine is in book */
12194             int depth = pvInfoList[index].depth;
12195             int score = pvInfoList[index].score;
12196             int in_book = 0;
12197
12198             if( depth <= 2 ) {
12199                 in_book = 1;
12200             }
12201             else if( score == 0 && depth == 63 ) {
12202                 in_book = 1; /* Zappa */
12203             }
12204             else if( score == 2 && depth == 99 ) {
12205                 in_book = 1; /* Abrok */
12206             }
12207
12208             has_book_hit += in_book;
12209
12210             if( ! in_book ) {
12211                 result = index;
12212
12213                 break;
12214             }
12215
12216             index += 2;
12217         }
12218     }
12219
12220     return result;
12221 }
12222
12223 void
12224 GetOutOfBookInfo (char * buf)
12225 {
12226     int oob[2];
12227     int i;
12228     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12229
12230     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12231     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12232
12233     *buf = '\0';
12234
12235     if( oob[0] >= 0 || oob[1] >= 0 ) {
12236         for( i=0; i<2; i++ ) {
12237             int idx = oob[i];
12238
12239             if( idx >= 0 ) {
12240                 if( i > 0 && oob[0] >= 0 ) {
12241                     strcat( buf, "   " );
12242                 }
12243
12244                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12245                 sprintf( buf+strlen(buf), "%s%.2f",
12246                     pvInfoList[idx].score >= 0 ? "+" : "",
12247                     pvInfoList[idx].score / 100.0 );
12248             }
12249         }
12250     }
12251 }
12252
12253 /* Save game in PGN style and close the file */
12254 int
12255 SaveGamePGN (FILE *f)
12256 {
12257     int i, offset, linelen, newblock;
12258     time_t tm;
12259 //    char *movetext;
12260     char numtext[32];
12261     int movelen, numlen, blank;
12262     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12263
12264     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12265
12266     tm = time((time_t *) NULL);
12267
12268     PrintPGNTags(f, &gameInfo);
12269
12270     if (backwardMostMove > 0 || startedFromSetupPosition) {
12271         char *fen = PositionToFEN(backwardMostMove, NULL);
12272         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12273         fprintf(f, "\n{--------------\n");
12274         PrintPosition(f, backwardMostMove);
12275         fprintf(f, "--------------}\n");
12276         free(fen);
12277     }
12278     else {
12279         /* [AS] Out of book annotation */
12280         if( appData.saveOutOfBookInfo ) {
12281             char buf[64];
12282
12283             GetOutOfBookInfo( buf );
12284
12285             if( buf[0] != '\0' ) {
12286                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12287             }
12288         }
12289
12290         fprintf(f, "\n");
12291     }
12292
12293     i = backwardMostMove;
12294     linelen = 0;
12295     newblock = TRUE;
12296
12297     while (i < forwardMostMove) {
12298         /* Print comments preceding this move */
12299         if (commentList[i] != NULL) {
12300             if (linelen > 0) fprintf(f, "\n");
12301             fprintf(f, "%s", commentList[i]);
12302             linelen = 0;
12303             newblock = TRUE;
12304         }
12305
12306         /* Format move number */
12307         if ((i % 2) == 0)
12308           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12309         else
12310           if (newblock)
12311             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12312           else
12313             numtext[0] = NULLCHAR;
12314
12315         numlen = strlen(numtext);
12316         newblock = FALSE;
12317
12318         /* Print move number */
12319         blank = linelen > 0 && numlen > 0;
12320         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12321             fprintf(f, "\n");
12322             linelen = 0;
12323             blank = 0;
12324         }
12325         if (blank) {
12326             fprintf(f, " ");
12327             linelen++;
12328         }
12329         fprintf(f, "%s", numtext);
12330         linelen += numlen;
12331
12332         /* Get move */
12333         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12334         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12335
12336         /* Print move */
12337         blank = linelen > 0 && movelen > 0;
12338         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12339             fprintf(f, "\n");
12340             linelen = 0;
12341             blank = 0;
12342         }
12343         if (blank) {
12344             fprintf(f, " ");
12345             linelen++;
12346         }
12347         fprintf(f, "%s", move_buffer);
12348         linelen += movelen;
12349
12350         /* [AS] Add PV info if present */
12351         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12352             /* [HGM] add time */
12353             char buf[MSG_SIZ]; int seconds;
12354
12355             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12356
12357             if( seconds <= 0)
12358               buf[0] = 0;
12359             else
12360               if( seconds < 30 )
12361                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12362               else
12363                 {
12364                   seconds = (seconds + 4)/10; // round to full seconds
12365                   if( seconds < 60 )
12366                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12367                   else
12368                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12369                 }
12370
12371             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12372                       pvInfoList[i].score >= 0 ? "+" : "",
12373                       pvInfoList[i].score / 100.0,
12374                       pvInfoList[i].depth,
12375                       buf );
12376
12377             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12378
12379             /* Print score/depth */
12380             blank = linelen > 0 && movelen > 0;
12381             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12382                 fprintf(f, "\n");
12383                 linelen = 0;
12384                 blank = 0;
12385             }
12386             if (blank) {
12387                 fprintf(f, " ");
12388                 linelen++;
12389             }
12390             fprintf(f, "%s", move_buffer);
12391             linelen += movelen;
12392         }
12393
12394         i++;
12395     }
12396
12397     /* Start a new line */
12398     if (linelen > 0) fprintf(f, "\n");
12399
12400     /* Print comments after last move */
12401     if (commentList[i] != NULL) {
12402         fprintf(f, "%s\n", commentList[i]);
12403     }
12404
12405     /* Print result */
12406     if (gameInfo.resultDetails != NULL &&
12407         gameInfo.resultDetails[0] != NULLCHAR) {
12408         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12409                 PGNResult(gameInfo.result));
12410     } else {
12411         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12412     }
12413
12414     fclose(f);
12415     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12416     return TRUE;
12417 }
12418
12419 /* Save game in old style and close the file */
12420 int
12421 SaveGameOldStyle (FILE *f)
12422 {
12423     int i, offset;
12424     time_t tm;
12425
12426     tm = time((time_t *) NULL);
12427
12428     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12429     PrintOpponents(f);
12430
12431     if (backwardMostMove > 0 || startedFromSetupPosition) {
12432         fprintf(f, "\n[--------------\n");
12433         PrintPosition(f, backwardMostMove);
12434         fprintf(f, "--------------]\n");
12435     } else {
12436         fprintf(f, "\n");
12437     }
12438
12439     i = backwardMostMove;
12440     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12441
12442     while (i < forwardMostMove) {
12443         if (commentList[i] != NULL) {
12444             fprintf(f, "[%s]\n", commentList[i]);
12445         }
12446
12447         if ((i % 2) == 1) {
12448             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12449             i++;
12450         } else {
12451             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12452             i++;
12453             if (commentList[i] != NULL) {
12454                 fprintf(f, "\n");
12455                 continue;
12456             }
12457             if (i >= forwardMostMove) {
12458                 fprintf(f, "\n");
12459                 break;
12460             }
12461             fprintf(f, "%s\n", parseList[i]);
12462             i++;
12463         }
12464     }
12465
12466     if (commentList[i] != NULL) {
12467         fprintf(f, "[%s]\n", commentList[i]);
12468     }
12469
12470     /* This isn't really the old style, but it's close enough */
12471     if (gameInfo.resultDetails != NULL &&
12472         gameInfo.resultDetails[0] != NULLCHAR) {
12473         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12474                 gameInfo.resultDetails);
12475     } else {
12476         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12477     }
12478
12479     fclose(f);
12480     return TRUE;
12481 }
12482
12483 /* Save the current game to open file f and close the file */
12484 int
12485 SaveGame (FILE *f, int dummy, char *dummy2)
12486 {
12487     if (gameMode == EditPosition) EditPositionDone(TRUE);
12488     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12489     if (appData.oldSaveStyle)
12490       return SaveGameOldStyle(f);
12491     else
12492       return SaveGamePGN(f);
12493 }
12494
12495 /* Save the current position to the given file */
12496 int
12497 SavePositionToFile (char *filename)
12498 {
12499     FILE *f;
12500     char buf[MSG_SIZ];
12501
12502     if (strcmp(filename, "-") == 0) {
12503         return SavePosition(stdout, 0, NULL);
12504     } else {
12505         f = fopen(filename, "a");
12506         if (f == NULL) {
12507             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12508             DisplayError(buf, errno);
12509             return FALSE;
12510         } else {
12511             safeStrCpy(buf, lastMsg, MSG_SIZ);
12512             DisplayMessage(_("Waiting for access to save file"), "");
12513             flock(fileno(f), LOCK_EX); // [HGM] lock
12514             DisplayMessage(_("Saving position"), "");
12515             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12516             SavePosition(f, 0, NULL);
12517             DisplayMessage(buf, "");
12518             return TRUE;
12519         }
12520     }
12521 }
12522
12523 /* Save the current position to the given open file and close the file */
12524 int
12525 SavePosition (FILE *f, int dummy, char *dummy2)
12526 {
12527     time_t tm;
12528     char *fen;
12529
12530     if (gameMode == EditPosition) EditPositionDone(TRUE);
12531     if (appData.oldSaveStyle) {
12532         tm = time((time_t *) NULL);
12533
12534         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12535         PrintOpponents(f);
12536         fprintf(f, "[--------------\n");
12537         PrintPosition(f, currentMove);
12538         fprintf(f, "--------------]\n");
12539     } else {
12540         fen = PositionToFEN(currentMove, NULL);
12541         fprintf(f, "%s\n", fen);
12542         free(fen);
12543     }
12544     fclose(f);
12545     return TRUE;
12546 }
12547
12548 void
12549 ReloadCmailMsgEvent (int unregister)
12550 {
12551 #if !WIN32
12552     static char *inFilename = NULL;
12553     static char *outFilename;
12554     int i;
12555     struct stat inbuf, outbuf;
12556     int status;
12557
12558     /* Any registered moves are unregistered if unregister is set, */
12559     /* i.e. invoked by the signal handler */
12560     if (unregister) {
12561         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12562             cmailMoveRegistered[i] = FALSE;
12563             if (cmailCommentList[i] != NULL) {
12564                 free(cmailCommentList[i]);
12565                 cmailCommentList[i] = NULL;
12566             }
12567         }
12568         nCmailMovesRegistered = 0;
12569     }
12570
12571     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12572         cmailResult[i] = CMAIL_NOT_RESULT;
12573     }
12574     nCmailResults = 0;
12575
12576     if (inFilename == NULL) {
12577         /* Because the filenames are static they only get malloced once  */
12578         /* and they never get freed                                      */
12579         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12580         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12581
12582         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12583         sprintf(outFilename, "%s.out", appData.cmailGameName);
12584     }
12585
12586     status = stat(outFilename, &outbuf);
12587     if (status < 0) {
12588         cmailMailedMove = FALSE;
12589     } else {
12590         status = stat(inFilename, &inbuf);
12591         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12592     }
12593
12594     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12595        counts the games, notes how each one terminated, etc.
12596
12597        It would be nice to remove this kludge and instead gather all
12598        the information while building the game list.  (And to keep it
12599        in the game list nodes instead of having a bunch of fixed-size
12600        parallel arrays.)  Note this will require getting each game's
12601        termination from the PGN tags, as the game list builder does
12602        not process the game moves.  --mann
12603        */
12604     cmailMsgLoaded = TRUE;
12605     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12606
12607     /* Load first game in the file or popup game menu */
12608     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12609
12610 #endif /* !WIN32 */
12611     return;
12612 }
12613
12614 int
12615 RegisterMove ()
12616 {
12617     FILE *f;
12618     char string[MSG_SIZ];
12619
12620     if (   cmailMailedMove
12621         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12622         return TRUE;            /* Allow free viewing  */
12623     }
12624
12625     /* Unregister move to ensure that we don't leave RegisterMove        */
12626     /* with the move registered when the conditions for registering no   */
12627     /* longer hold                                                       */
12628     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12629         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12630         nCmailMovesRegistered --;
12631
12632         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12633           {
12634               free(cmailCommentList[lastLoadGameNumber - 1]);
12635               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12636           }
12637     }
12638
12639     if (cmailOldMove == -1) {
12640         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12641         return FALSE;
12642     }
12643
12644     if (currentMove > cmailOldMove + 1) {
12645         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12646         return FALSE;
12647     }
12648
12649     if (currentMove < cmailOldMove) {
12650         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12651         return FALSE;
12652     }
12653
12654     if (forwardMostMove > currentMove) {
12655         /* Silently truncate extra moves */
12656         TruncateGame();
12657     }
12658
12659     if (   (currentMove == cmailOldMove + 1)
12660         || (   (currentMove == cmailOldMove)
12661             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12662                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12663         if (gameInfo.result != GameUnfinished) {
12664             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12665         }
12666
12667         if (commentList[currentMove] != NULL) {
12668             cmailCommentList[lastLoadGameNumber - 1]
12669               = StrSave(commentList[currentMove]);
12670         }
12671         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12672
12673         if (appData.debugMode)
12674           fprintf(debugFP, "Saving %s for game %d\n",
12675                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12676
12677         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12678
12679         f = fopen(string, "w");
12680         if (appData.oldSaveStyle) {
12681             SaveGameOldStyle(f); /* also closes the file */
12682
12683             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12684             f = fopen(string, "w");
12685             SavePosition(f, 0, NULL); /* also closes the file */
12686         } else {
12687             fprintf(f, "{--------------\n");
12688             PrintPosition(f, currentMove);
12689             fprintf(f, "--------------}\n\n");
12690
12691             SaveGame(f, 0, NULL); /* also closes the file*/
12692         }
12693
12694         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12695         nCmailMovesRegistered ++;
12696     } else if (nCmailGames == 1) {
12697         DisplayError(_("You have not made a move yet"), 0);
12698         return FALSE;
12699     }
12700
12701     return TRUE;
12702 }
12703
12704 void
12705 MailMoveEvent ()
12706 {
12707 #if !WIN32
12708     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12709     FILE *commandOutput;
12710     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12711     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12712     int nBuffers;
12713     int i;
12714     int archived;
12715     char *arcDir;
12716
12717     if (! cmailMsgLoaded) {
12718         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12719         return;
12720     }
12721
12722     if (nCmailGames == nCmailResults) {
12723         DisplayError(_("No unfinished games"), 0);
12724         return;
12725     }
12726
12727 #if CMAIL_PROHIBIT_REMAIL
12728     if (cmailMailedMove) {
12729       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);
12730         DisplayError(msg, 0);
12731         return;
12732     }
12733 #endif
12734
12735     if (! (cmailMailedMove || RegisterMove())) return;
12736
12737     if (   cmailMailedMove
12738         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12739       snprintf(string, MSG_SIZ, partCommandString,
12740                appData.debugMode ? " -v" : "", appData.cmailGameName);
12741         commandOutput = popen(string, "r");
12742
12743         if (commandOutput == NULL) {
12744             DisplayError(_("Failed to invoke cmail"), 0);
12745         } else {
12746             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12747                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12748             }
12749             if (nBuffers > 1) {
12750                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12751                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12752                 nBytes = MSG_SIZ - 1;
12753             } else {
12754                 (void) memcpy(msg, buffer, nBytes);
12755             }
12756             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12757
12758             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12759                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12760
12761                 archived = TRUE;
12762                 for (i = 0; i < nCmailGames; i ++) {
12763                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12764                         archived = FALSE;
12765                     }
12766                 }
12767                 if (   archived
12768                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12769                         != NULL)) {
12770                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12771                            arcDir,
12772                            appData.cmailGameName,
12773                            gameInfo.date);
12774                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12775                     cmailMsgLoaded = FALSE;
12776                 }
12777             }
12778
12779             DisplayInformation(msg);
12780             pclose(commandOutput);
12781         }
12782     } else {
12783         if ((*cmailMsg) != '\0') {
12784             DisplayInformation(cmailMsg);
12785         }
12786     }
12787
12788     return;
12789 #endif /* !WIN32 */
12790 }
12791
12792 char *
12793 CmailMsg ()
12794 {
12795 #if WIN32
12796     return NULL;
12797 #else
12798     int  prependComma = 0;
12799     char number[5];
12800     char string[MSG_SIZ];       /* Space for game-list */
12801     int  i;
12802
12803     if (!cmailMsgLoaded) return "";
12804
12805     if (cmailMailedMove) {
12806       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12807     } else {
12808         /* Create a list of games left */
12809       snprintf(string, MSG_SIZ, "[");
12810         for (i = 0; i < nCmailGames; i ++) {
12811             if (! (   cmailMoveRegistered[i]
12812                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12813                 if (prependComma) {
12814                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12815                 } else {
12816                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12817                     prependComma = 1;
12818                 }
12819
12820                 strcat(string, number);
12821             }
12822         }
12823         strcat(string, "]");
12824
12825         if (nCmailMovesRegistered + nCmailResults == 0) {
12826             switch (nCmailGames) {
12827               case 1:
12828                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12829                 break;
12830
12831               case 2:
12832                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12833                 break;
12834
12835               default:
12836                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12837                          nCmailGames);
12838                 break;
12839             }
12840         } else {
12841             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12842               case 1:
12843                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12844                          string);
12845                 break;
12846
12847               case 0:
12848                 if (nCmailResults == nCmailGames) {
12849                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12850                 } else {
12851                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12852                 }
12853                 break;
12854
12855               default:
12856                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12857                          string);
12858             }
12859         }
12860     }
12861     return cmailMsg;
12862 #endif /* WIN32 */
12863 }
12864
12865 void
12866 ResetGameEvent ()
12867 {
12868     if (gameMode == Training)
12869       SetTrainingModeOff();
12870
12871     Reset(TRUE, TRUE);
12872     cmailMsgLoaded = FALSE;
12873     if (appData.icsActive) {
12874       SendToICS(ics_prefix);
12875       SendToICS("refresh\n");
12876     }
12877 }
12878
12879 void
12880 ExitEvent (int status)
12881 {
12882     exiting++;
12883     if (exiting > 2) {
12884       /* Give up on clean exit */
12885       exit(status);
12886     }
12887     if (exiting > 1) {
12888       /* Keep trying for clean exit */
12889       return;
12890     }
12891
12892     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12893
12894     if (telnetISR != NULL) {
12895       RemoveInputSource(telnetISR);
12896     }
12897     if (icsPR != NoProc) {
12898       DestroyChildProcess(icsPR, TRUE);
12899     }
12900
12901     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12902     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12903
12904     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12905     /* make sure this other one finishes before killing it!                  */
12906     if(endingGame) { int count = 0;
12907         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12908         while(endingGame && count++ < 10) DoSleep(1);
12909         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12910     }
12911
12912     /* Kill off chess programs */
12913     if (first.pr != NoProc) {
12914         ExitAnalyzeMode();
12915
12916         DoSleep( appData.delayBeforeQuit );
12917         SendToProgram("quit\n", &first);
12918         DoSleep( appData.delayAfterQuit );
12919         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12920     }
12921     if (second.pr != NoProc) {
12922         DoSleep( appData.delayBeforeQuit );
12923         SendToProgram("quit\n", &second);
12924         DoSleep( appData.delayAfterQuit );
12925         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12926     }
12927     if (first.isr != NULL) {
12928         RemoveInputSource(first.isr);
12929     }
12930     if (second.isr != NULL) {
12931         RemoveInputSource(second.isr);
12932     }
12933
12934     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12935     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12936
12937     ShutDownFrontEnd();
12938     exit(status);
12939 }
12940
12941 void
12942 PauseEvent ()
12943 {
12944     if (appData.debugMode)
12945         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12946     if (pausing) {
12947         pausing = FALSE;
12948         ModeHighlight();
12949         if (gameMode == MachinePlaysWhite ||
12950             gameMode == MachinePlaysBlack) {
12951             StartClocks();
12952         } else {
12953             DisplayBothClocks();
12954         }
12955         if (gameMode == PlayFromGameFile) {
12956             if (appData.timeDelay >= 0)
12957                 AutoPlayGameLoop();
12958         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12959             Reset(FALSE, TRUE);
12960             SendToICS(ics_prefix);
12961             SendToICS("refresh\n");
12962         } else if (currentMove < forwardMostMove) {
12963             ForwardInner(forwardMostMove);
12964         }
12965         pauseExamInvalid = FALSE;
12966     } else {
12967         switch (gameMode) {
12968           default:
12969             return;
12970           case IcsExamining:
12971             pauseExamForwardMostMove = forwardMostMove;
12972             pauseExamInvalid = FALSE;
12973             /* fall through */
12974           case IcsObserving:
12975           case IcsPlayingWhite:
12976           case IcsPlayingBlack:
12977             pausing = TRUE;
12978             ModeHighlight();
12979             return;
12980           case PlayFromGameFile:
12981             (void) StopLoadGameTimer();
12982             pausing = TRUE;
12983             ModeHighlight();
12984             break;
12985           case BeginningOfGame:
12986             if (appData.icsActive) return;
12987             /* else fall through */
12988           case MachinePlaysWhite:
12989           case MachinePlaysBlack:
12990           case TwoMachinesPlay:
12991             if (forwardMostMove == 0)
12992               return;           /* don't pause if no one has moved */
12993             if ((gameMode == MachinePlaysWhite &&
12994                  !WhiteOnMove(forwardMostMove)) ||
12995                 (gameMode == MachinePlaysBlack &&
12996                  WhiteOnMove(forwardMostMove))) {
12997                 StopClocks();
12998             }
12999             pausing = TRUE;
13000             ModeHighlight();
13001             break;
13002         }
13003     }
13004 }
13005
13006 void
13007 EditCommentEvent ()
13008 {
13009     char title[MSG_SIZ];
13010
13011     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13012       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13013     } else {
13014       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13015                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13016                parseList[currentMove - 1]);
13017     }
13018
13019     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13020 }
13021
13022
13023 void
13024 EditTagsEvent ()
13025 {
13026     char *tags = PGNTags(&gameInfo);
13027     bookUp = FALSE;
13028     EditTagsPopUp(tags, NULL);
13029     free(tags);
13030 }
13031
13032 void
13033 AnalyzeModeEvent ()
13034 {
13035     if (appData.noChessProgram || gameMode == AnalyzeMode)
13036       return;
13037
13038     if (gameMode != AnalyzeFile) {
13039         if (!appData.icsEngineAnalyze) {
13040                EditGameEvent();
13041                if (gameMode != EditGame) return;
13042         }
13043         ResurrectChessProgram();
13044         SendToProgram("analyze\n", &first);
13045         first.analyzing = TRUE;
13046         /*first.maybeThinking = TRUE;*/
13047         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13048         EngineOutputPopUp();
13049     }
13050     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13051     pausing = FALSE;
13052     ModeHighlight();
13053     SetGameInfo();
13054
13055     StartAnalysisClock();
13056     GetTimeMark(&lastNodeCountTime);
13057     lastNodeCount = 0;
13058 }
13059
13060 void
13061 AnalyzeFileEvent ()
13062 {
13063     if (appData.noChessProgram || gameMode == AnalyzeFile)
13064       return;
13065
13066     if (gameMode != AnalyzeMode) {
13067         EditGameEvent();
13068         if (gameMode != EditGame) return;
13069         ResurrectChessProgram();
13070         SendToProgram("analyze\n", &first);
13071         first.analyzing = TRUE;
13072         /*first.maybeThinking = TRUE;*/
13073         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13074         EngineOutputPopUp();
13075     }
13076     gameMode = AnalyzeFile;
13077     pausing = FALSE;
13078     ModeHighlight();
13079     SetGameInfo();
13080
13081     StartAnalysisClock();
13082     GetTimeMark(&lastNodeCountTime);
13083     lastNodeCount = 0;
13084     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13085 }
13086
13087 void
13088 MachineWhiteEvent ()
13089 {
13090     char buf[MSG_SIZ];
13091     char *bookHit = NULL;
13092
13093     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13094       return;
13095
13096
13097     if (gameMode == PlayFromGameFile ||
13098         gameMode == TwoMachinesPlay  ||
13099         gameMode == Training         ||
13100         gameMode == AnalyzeMode      ||
13101         gameMode == EndOfGame)
13102         EditGameEvent();
13103
13104     if (gameMode == EditPosition)
13105         EditPositionDone(TRUE);
13106
13107     if (!WhiteOnMove(currentMove)) {
13108         DisplayError(_("It is not White's turn"), 0);
13109         return;
13110     }
13111
13112     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13113       ExitAnalyzeMode();
13114
13115     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13116         gameMode == AnalyzeFile)
13117         TruncateGame();
13118
13119     ResurrectChessProgram();    /* in case it isn't running */
13120     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13121         gameMode = MachinePlaysWhite;
13122         ResetClocks();
13123     } else
13124     gameMode = MachinePlaysWhite;
13125     pausing = FALSE;
13126     ModeHighlight();
13127     SetGameInfo();
13128     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13129     DisplayTitle(buf);
13130     if (first.sendName) {
13131       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13132       SendToProgram(buf, &first);
13133     }
13134     if (first.sendTime) {
13135       if (first.useColors) {
13136         SendToProgram("black\n", &first); /*gnu kludge*/
13137       }
13138       SendTimeRemaining(&first, TRUE);
13139     }
13140     if (first.useColors) {
13141       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13142     }
13143     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13144     SetMachineThinkingEnables();
13145     first.maybeThinking = TRUE;
13146     StartClocks();
13147     firstMove = FALSE;
13148
13149     if (appData.autoFlipView && !flipView) {
13150       flipView = !flipView;
13151       DrawPosition(FALSE, NULL);
13152       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13153     }
13154
13155     if(bookHit) { // [HGM] book: simulate book reply
13156         static char bookMove[MSG_SIZ]; // a bit generous?
13157
13158         programStats.nodes = programStats.depth = programStats.time =
13159         programStats.score = programStats.got_only_move = 0;
13160         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13161
13162         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13163         strcat(bookMove, bookHit);
13164         HandleMachineMove(bookMove, &first);
13165     }
13166 }
13167
13168 void
13169 MachineBlackEvent ()
13170 {
13171   char buf[MSG_SIZ];
13172   char *bookHit = NULL;
13173
13174     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13175         return;
13176
13177
13178     if (gameMode == PlayFromGameFile ||
13179         gameMode == TwoMachinesPlay  ||
13180         gameMode == Training         ||
13181         gameMode == AnalyzeMode      ||
13182         gameMode == EndOfGame)
13183         EditGameEvent();
13184
13185     if (gameMode == EditPosition)
13186         EditPositionDone(TRUE);
13187
13188     if (WhiteOnMove(currentMove)) {
13189         DisplayError(_("It is not Black's turn"), 0);
13190         return;
13191     }
13192
13193     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13194       ExitAnalyzeMode();
13195
13196     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13197         gameMode == AnalyzeFile)
13198         TruncateGame();
13199
13200     ResurrectChessProgram();    /* in case it isn't running */
13201     gameMode = MachinePlaysBlack;
13202     pausing = FALSE;
13203     ModeHighlight();
13204     SetGameInfo();
13205     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13206     DisplayTitle(buf);
13207     if (first.sendName) {
13208       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13209       SendToProgram(buf, &first);
13210     }
13211     if (first.sendTime) {
13212       if (first.useColors) {
13213         SendToProgram("white\n", &first); /*gnu kludge*/
13214       }
13215       SendTimeRemaining(&first, FALSE);
13216     }
13217     if (first.useColors) {
13218       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13219     }
13220     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13221     SetMachineThinkingEnables();
13222     first.maybeThinking = TRUE;
13223     StartClocks();
13224
13225     if (appData.autoFlipView && flipView) {
13226       flipView = !flipView;
13227       DrawPosition(FALSE, NULL);
13228       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13229     }
13230     if(bookHit) { // [HGM] book: simulate book reply
13231         static char bookMove[MSG_SIZ]; // a bit generous?
13232
13233         programStats.nodes = programStats.depth = programStats.time =
13234         programStats.score = programStats.got_only_move = 0;
13235         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13236
13237         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13238         strcat(bookMove, bookHit);
13239         HandleMachineMove(bookMove, &first);
13240     }
13241 }
13242
13243
13244 void
13245 DisplayTwoMachinesTitle ()
13246 {
13247     char buf[MSG_SIZ];
13248     if (appData.matchGames > 0) {
13249         if(appData.tourneyFile[0]) {
13250           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13251                    gameInfo.white, _("vs."), gameInfo.black,
13252                    nextGame+1, appData.matchGames+1,
13253                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13254         } else 
13255         if (first.twoMachinesColor[0] == 'w') {
13256           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13257                    gameInfo.white, _("vs."),  gameInfo.black,
13258                    first.matchWins, second.matchWins,
13259                    matchGame - 1 - (first.matchWins + second.matchWins));
13260         } else {
13261           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13262                    gameInfo.white, _("vs."), gameInfo.black,
13263                    second.matchWins, first.matchWins,
13264                    matchGame - 1 - (first.matchWins + second.matchWins));
13265         }
13266     } else {
13267       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13268     }
13269     DisplayTitle(buf);
13270 }
13271
13272 void
13273 SettingsMenuIfReady ()
13274 {
13275   if (second.lastPing != second.lastPong) {
13276     DisplayMessage("", _("Waiting for second chess program"));
13277     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13278     return;
13279   }
13280   ThawUI();
13281   DisplayMessage("", "");
13282   SettingsPopUp(&second);
13283 }
13284
13285 int
13286 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13287 {
13288     char buf[MSG_SIZ];
13289     if (cps->pr == NoProc) {
13290         StartChessProgram(cps);
13291         if (cps->protocolVersion == 1) {
13292           retry();
13293         } else {
13294           /* kludge: allow timeout for initial "feature" command */
13295           FreezeUI();
13296           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13297           DisplayMessage("", buf);
13298           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13299         }
13300         return 1;
13301     }
13302     return 0;
13303 }
13304
13305 void
13306 TwoMachinesEvent P((void))
13307 {
13308     int i;
13309     char buf[MSG_SIZ];
13310     ChessProgramState *onmove;
13311     char *bookHit = NULL;
13312     static int stalling = 0;
13313     TimeMark now;
13314     long wait;
13315
13316     if (appData.noChessProgram) return;
13317
13318     switch (gameMode) {
13319       case TwoMachinesPlay:
13320         return;
13321       case MachinePlaysWhite:
13322       case MachinePlaysBlack:
13323         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13324             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13325             return;
13326         }
13327         /* fall through */
13328       case BeginningOfGame:
13329       case PlayFromGameFile:
13330       case EndOfGame:
13331         EditGameEvent();
13332         if (gameMode != EditGame) return;
13333         break;
13334       case EditPosition:
13335         EditPositionDone(TRUE);
13336         break;
13337       case AnalyzeMode:
13338       case AnalyzeFile:
13339         ExitAnalyzeMode();
13340         break;
13341       case EditGame:
13342       default:
13343         break;
13344     }
13345
13346 //    forwardMostMove = currentMove;
13347     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13348
13349     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13350
13351     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13352     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13353       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13354       return;
13355     }
13356     if(!stalling) {
13357       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13358       SendToProgram("force\n", &second);
13359       stalling = 1;
13360       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13361       return;
13362     }
13363     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13364     if(appData.matchPause>10000 || appData.matchPause<10)
13365                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13366     wait = SubtractTimeMarks(&now, &pauseStart);
13367     if(wait < appData.matchPause) {
13368         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13369         return;
13370     }
13371     stalling = 0;
13372     DisplayMessage("", "");
13373     if (startedFromSetupPosition) {
13374         SendBoard(&second, backwardMostMove);
13375     if (appData.debugMode) {
13376         fprintf(debugFP, "Two Machines\n");
13377     }
13378     }
13379     for (i = backwardMostMove; i < forwardMostMove; i++) {
13380         SendMoveToProgram(i, &second);
13381     }
13382
13383     gameMode = TwoMachinesPlay;
13384     pausing = FALSE;
13385     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13386     SetGameInfo();
13387     DisplayTwoMachinesTitle();
13388     firstMove = TRUE;
13389     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13390         onmove = &first;
13391     } else {
13392         onmove = &second;
13393     }
13394     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13395     SendToProgram(first.computerString, &first);
13396     if (first.sendName) {
13397       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13398       SendToProgram(buf, &first);
13399     }
13400     SendToProgram(second.computerString, &second);
13401     if (second.sendName) {
13402       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13403       SendToProgram(buf, &second);
13404     }
13405
13406     ResetClocks();
13407     if (!first.sendTime || !second.sendTime) {
13408         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13409         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13410     }
13411     if (onmove->sendTime) {
13412       if (onmove->useColors) {
13413         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13414       }
13415       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13416     }
13417     if (onmove->useColors) {
13418       SendToProgram(onmove->twoMachinesColor, onmove);
13419     }
13420     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13421 //    SendToProgram("go\n", onmove);
13422     onmove->maybeThinking = TRUE;
13423     SetMachineThinkingEnables();
13424
13425     StartClocks();
13426
13427     if(bookHit) { // [HGM] book: simulate book reply
13428         static char bookMove[MSG_SIZ]; // a bit generous?
13429
13430         programStats.nodes = programStats.depth = programStats.time =
13431         programStats.score = programStats.got_only_move = 0;
13432         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13433
13434         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13435         strcat(bookMove, bookHit);
13436         savedMessage = bookMove; // args for deferred call
13437         savedState = onmove;
13438         ScheduleDelayedEvent(DeferredBookMove, 1);
13439     }
13440 }
13441
13442 void
13443 TrainingEvent ()
13444 {
13445     if (gameMode == Training) {
13446       SetTrainingModeOff();
13447       gameMode = PlayFromGameFile;
13448       DisplayMessage("", _("Training mode off"));
13449     } else {
13450       gameMode = Training;
13451       animateTraining = appData.animate;
13452
13453       /* make sure we are not already at the end of the game */
13454       if (currentMove < forwardMostMove) {
13455         SetTrainingModeOn();
13456         DisplayMessage("", _("Training mode on"));
13457       } else {
13458         gameMode = PlayFromGameFile;
13459         DisplayError(_("Already at end of game"), 0);
13460       }
13461     }
13462     ModeHighlight();
13463 }
13464
13465 void
13466 IcsClientEvent ()
13467 {
13468     if (!appData.icsActive) return;
13469     switch (gameMode) {
13470       case IcsPlayingWhite:
13471       case IcsPlayingBlack:
13472       case IcsObserving:
13473       case IcsIdle:
13474       case BeginningOfGame:
13475       case IcsExamining:
13476         return;
13477
13478       case EditGame:
13479         break;
13480
13481       case EditPosition:
13482         EditPositionDone(TRUE);
13483         break;
13484
13485       case AnalyzeMode:
13486       case AnalyzeFile:
13487         ExitAnalyzeMode();
13488         break;
13489
13490       default:
13491         EditGameEvent();
13492         break;
13493     }
13494
13495     gameMode = IcsIdle;
13496     ModeHighlight();
13497     return;
13498 }
13499
13500 void
13501 EditGameEvent ()
13502 {
13503     int i;
13504
13505     switch (gameMode) {
13506       case Training:
13507         SetTrainingModeOff();
13508         break;
13509       case MachinePlaysWhite:
13510       case MachinePlaysBlack:
13511       case BeginningOfGame:
13512         SendToProgram("force\n", &first);
13513         SetUserThinkingEnables();
13514         break;
13515       case PlayFromGameFile:
13516         (void) StopLoadGameTimer();
13517         if (gameFileFP != NULL) {
13518             gameFileFP = NULL;
13519         }
13520         break;
13521       case EditPosition:
13522         EditPositionDone(TRUE);
13523         break;
13524       case AnalyzeMode:
13525       case AnalyzeFile:
13526         ExitAnalyzeMode();
13527         SendToProgram("force\n", &first);
13528         break;
13529       case TwoMachinesPlay:
13530         GameEnds(EndOfFile, NULL, GE_PLAYER);
13531         ResurrectChessProgram();
13532         SetUserThinkingEnables();
13533         break;
13534       case EndOfGame:
13535         ResurrectChessProgram();
13536         break;
13537       case IcsPlayingBlack:
13538       case IcsPlayingWhite:
13539         DisplayError(_("Warning: You are still playing a game"), 0);
13540         break;
13541       case IcsObserving:
13542         DisplayError(_("Warning: You are still observing a game"), 0);
13543         break;
13544       case IcsExamining:
13545         DisplayError(_("Warning: You are still examining a game"), 0);
13546         break;
13547       case IcsIdle:
13548         break;
13549       case EditGame:
13550       default:
13551         return;
13552     }
13553
13554     pausing = FALSE;
13555     StopClocks();
13556     first.offeredDraw = second.offeredDraw = 0;
13557
13558     if (gameMode == PlayFromGameFile) {
13559         whiteTimeRemaining = timeRemaining[0][currentMove];
13560         blackTimeRemaining = timeRemaining[1][currentMove];
13561         DisplayTitle("");
13562     }
13563
13564     if (gameMode == MachinePlaysWhite ||
13565         gameMode == MachinePlaysBlack ||
13566         gameMode == TwoMachinesPlay ||
13567         gameMode == EndOfGame) {
13568         i = forwardMostMove;
13569         while (i > currentMove) {
13570             SendToProgram("undo\n", &first);
13571             i--;
13572         }
13573         if(!adjustedClock) {
13574         whiteTimeRemaining = timeRemaining[0][currentMove];
13575         blackTimeRemaining = timeRemaining[1][currentMove];
13576         DisplayBothClocks();
13577         }
13578         if (whiteFlag || blackFlag) {
13579             whiteFlag = blackFlag = 0;
13580         }
13581         DisplayTitle("");
13582     }
13583
13584     gameMode = EditGame;
13585     ModeHighlight();
13586     SetGameInfo();
13587 }
13588
13589
13590 void
13591 EditPositionEvent ()
13592 {
13593     if (gameMode == EditPosition) {
13594         EditGameEvent();
13595         return;
13596     }
13597
13598     EditGameEvent();
13599     if (gameMode != EditGame) return;
13600
13601     gameMode = EditPosition;
13602     ModeHighlight();
13603     SetGameInfo();
13604     if (currentMove > 0)
13605       CopyBoard(boards[0], boards[currentMove]);
13606
13607     blackPlaysFirst = !WhiteOnMove(currentMove);
13608     ResetClocks();
13609     currentMove = forwardMostMove = backwardMostMove = 0;
13610     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13611     DisplayMove(-1);
13612 }
13613
13614 void
13615 ExitAnalyzeMode ()
13616 {
13617     /* [DM] icsEngineAnalyze - possible call from other functions */
13618     if (appData.icsEngineAnalyze) {
13619         appData.icsEngineAnalyze = FALSE;
13620
13621         DisplayMessage("",_("Close ICS engine analyze..."));
13622     }
13623     if (first.analysisSupport && first.analyzing) {
13624       SendToProgram("exit\n", &first);
13625       first.analyzing = FALSE;
13626     }
13627     thinkOutput[0] = NULLCHAR;
13628 }
13629
13630 void
13631 EditPositionDone (Boolean fakeRights)
13632 {
13633     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13634
13635     startedFromSetupPosition = TRUE;
13636     InitChessProgram(&first, FALSE);
13637     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13638       boards[0][EP_STATUS] = EP_NONE;
13639       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13640     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13641         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13642         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13643       } else boards[0][CASTLING][2] = NoRights;
13644     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13645         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13646         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13647       } else boards[0][CASTLING][5] = NoRights;
13648     }
13649     SendToProgram("force\n", &first);
13650     if (blackPlaysFirst) {
13651         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13652         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13653         currentMove = forwardMostMove = backwardMostMove = 1;
13654         CopyBoard(boards[1], boards[0]);
13655     } else {
13656         currentMove = forwardMostMove = backwardMostMove = 0;
13657     }
13658     SendBoard(&first, forwardMostMove);
13659     if (appData.debugMode) {
13660         fprintf(debugFP, "EditPosDone\n");
13661     }
13662     DisplayTitle("");
13663     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13664     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13665     gameMode = EditGame;
13666     ModeHighlight();
13667     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13668     ClearHighlights(); /* [AS] */
13669 }
13670
13671 /* Pause for `ms' milliseconds */
13672 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13673 void
13674 TimeDelay (long ms)
13675 {
13676     TimeMark m1, m2;
13677
13678     GetTimeMark(&m1);
13679     do {
13680         GetTimeMark(&m2);
13681     } while (SubtractTimeMarks(&m2, &m1) < ms);
13682 }
13683
13684 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13685 void
13686 SendMultiLineToICS (char *buf)
13687 {
13688     char temp[MSG_SIZ+1], *p;
13689     int len;
13690
13691     len = strlen(buf);
13692     if (len > MSG_SIZ)
13693       len = MSG_SIZ;
13694
13695     strncpy(temp, buf, len);
13696     temp[len] = 0;
13697
13698     p = temp;
13699     while (*p) {
13700         if (*p == '\n' || *p == '\r')
13701           *p = ' ';
13702         ++p;
13703     }
13704
13705     strcat(temp, "\n");
13706     SendToICS(temp);
13707     SendToPlayer(temp, strlen(temp));
13708 }
13709
13710 void
13711 SetWhiteToPlayEvent ()
13712 {
13713     if (gameMode == EditPosition) {
13714         blackPlaysFirst = FALSE;
13715         DisplayBothClocks();    /* works because currentMove is 0 */
13716     } else if (gameMode == IcsExamining) {
13717         SendToICS(ics_prefix);
13718         SendToICS("tomove white\n");
13719     }
13720 }
13721
13722 void
13723 SetBlackToPlayEvent ()
13724 {
13725     if (gameMode == EditPosition) {
13726         blackPlaysFirst = TRUE;
13727         currentMove = 1;        /* kludge */
13728         DisplayBothClocks();
13729         currentMove = 0;
13730     } else if (gameMode == IcsExamining) {
13731         SendToICS(ics_prefix);
13732         SendToICS("tomove black\n");
13733     }
13734 }
13735
13736 void
13737 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13738 {
13739     char buf[MSG_SIZ];
13740     ChessSquare piece = boards[0][y][x];
13741
13742     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13743
13744     switch (selection) {
13745       case ClearBoard:
13746         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13747             SendToICS(ics_prefix);
13748             SendToICS("bsetup clear\n");
13749         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13750             SendToICS(ics_prefix);
13751             SendToICS("clearboard\n");
13752         } else {
13753             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13754                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13755                 for (y = 0; y < BOARD_HEIGHT; y++) {
13756                     if (gameMode == IcsExamining) {
13757                         if (boards[currentMove][y][x] != EmptySquare) {
13758                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13759                                     AAA + x, ONE + y);
13760                             SendToICS(buf);
13761                         }
13762                     } else {
13763                         boards[0][y][x] = p;
13764                     }
13765                 }
13766             }
13767         }
13768         if (gameMode == EditPosition) {
13769             DrawPosition(FALSE, boards[0]);
13770         }
13771         break;
13772
13773       case WhitePlay:
13774         SetWhiteToPlayEvent();
13775         break;
13776
13777       case BlackPlay:
13778         SetBlackToPlayEvent();
13779         break;
13780
13781       case EmptySquare:
13782         if (gameMode == IcsExamining) {
13783             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13784             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13785             SendToICS(buf);
13786         } else {
13787             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13788                 if(x == BOARD_LEFT-2) {
13789                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13790                     boards[0][y][1] = 0;
13791                 } else
13792                 if(x == BOARD_RGHT+1) {
13793                     if(y >= gameInfo.holdingsSize) break;
13794                     boards[0][y][BOARD_WIDTH-2] = 0;
13795                 } else break;
13796             }
13797             boards[0][y][x] = EmptySquare;
13798             DrawPosition(FALSE, boards[0]);
13799         }
13800         break;
13801
13802       case PromotePiece:
13803         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13804            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13805             selection = (ChessSquare) (PROMOTED piece);
13806         } else if(piece == EmptySquare) selection = WhiteSilver;
13807         else selection = (ChessSquare)((int)piece - 1);
13808         goto defaultlabel;
13809
13810       case DemotePiece:
13811         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13812            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13813             selection = (ChessSquare) (DEMOTED piece);
13814         } else if(piece == EmptySquare) selection = BlackSilver;
13815         else selection = (ChessSquare)((int)piece + 1);
13816         goto defaultlabel;
13817
13818       case WhiteQueen:
13819       case BlackQueen:
13820         if(gameInfo.variant == VariantShatranj ||
13821            gameInfo.variant == VariantXiangqi  ||
13822            gameInfo.variant == VariantCourier  ||
13823            gameInfo.variant == VariantMakruk     )
13824             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13825         goto defaultlabel;
13826
13827       case WhiteKing:
13828       case BlackKing:
13829         if(gameInfo.variant == VariantXiangqi)
13830             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13831         if(gameInfo.variant == VariantKnightmate)
13832             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13833       default:
13834         defaultlabel:
13835         if (gameMode == IcsExamining) {
13836             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13837             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13838                      PieceToChar(selection), AAA + x, ONE + y);
13839             SendToICS(buf);
13840         } else {
13841             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13842                 int n;
13843                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13844                     n = PieceToNumber(selection - BlackPawn);
13845                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13846                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13847                     boards[0][BOARD_HEIGHT-1-n][1]++;
13848                 } else
13849                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13850                     n = PieceToNumber(selection);
13851                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13852                     boards[0][n][BOARD_WIDTH-1] = selection;
13853                     boards[0][n][BOARD_WIDTH-2]++;
13854                 }
13855             } else
13856             boards[0][y][x] = selection;
13857             DrawPosition(TRUE, boards[0]);
13858         }
13859         break;
13860     }
13861 }
13862
13863
13864 void
13865 DropMenuEvent (ChessSquare selection, int x, int y)
13866 {
13867     ChessMove moveType;
13868
13869     switch (gameMode) {
13870       case IcsPlayingWhite:
13871       case MachinePlaysBlack:
13872         if (!WhiteOnMove(currentMove)) {
13873             DisplayMoveError(_("It is Black's turn"));
13874             return;
13875         }
13876         moveType = WhiteDrop;
13877         break;
13878       case IcsPlayingBlack:
13879       case MachinePlaysWhite:
13880         if (WhiteOnMove(currentMove)) {
13881             DisplayMoveError(_("It is White's turn"));
13882             return;
13883         }
13884         moveType = BlackDrop;
13885         break;
13886       case EditGame:
13887         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13888         break;
13889       default:
13890         return;
13891     }
13892
13893     if (moveType == BlackDrop && selection < BlackPawn) {
13894       selection = (ChessSquare) ((int) selection
13895                                  + (int) BlackPawn - (int) WhitePawn);
13896     }
13897     if (boards[currentMove][y][x] != EmptySquare) {
13898         DisplayMoveError(_("That square is occupied"));
13899         return;
13900     }
13901
13902     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13903 }
13904
13905 void
13906 AcceptEvent ()
13907 {
13908     /* Accept a pending offer of any kind from opponent */
13909
13910     if (appData.icsActive) {
13911         SendToICS(ics_prefix);
13912         SendToICS("accept\n");
13913     } else if (cmailMsgLoaded) {
13914         if (currentMove == cmailOldMove &&
13915             commentList[cmailOldMove] != NULL &&
13916             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13917                    "Black offers a draw" : "White offers a draw")) {
13918             TruncateGame();
13919             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13920             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13921         } else {
13922             DisplayError(_("There is no pending offer on this move"), 0);
13923             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13924         }
13925     } else {
13926         /* Not used for offers from chess program */
13927     }
13928 }
13929
13930 void
13931 DeclineEvent ()
13932 {
13933     /* Decline a pending offer of any kind from opponent */
13934
13935     if (appData.icsActive) {
13936         SendToICS(ics_prefix);
13937         SendToICS("decline\n");
13938     } else if (cmailMsgLoaded) {
13939         if (currentMove == cmailOldMove &&
13940             commentList[cmailOldMove] != NULL &&
13941             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13942                    "Black offers a draw" : "White offers a draw")) {
13943 #ifdef NOTDEF
13944             AppendComment(cmailOldMove, "Draw declined", TRUE);
13945             DisplayComment(cmailOldMove - 1, "Draw declined");
13946 #endif /*NOTDEF*/
13947         } else {
13948             DisplayError(_("There is no pending offer on this move"), 0);
13949         }
13950     } else {
13951         /* Not used for offers from chess program */
13952     }
13953 }
13954
13955 void
13956 RematchEvent ()
13957 {
13958     /* Issue ICS rematch command */
13959     if (appData.icsActive) {
13960         SendToICS(ics_prefix);
13961         SendToICS("rematch\n");
13962     }
13963 }
13964
13965 void
13966 CallFlagEvent ()
13967 {
13968     /* Call your opponent's flag (claim a win on time) */
13969     if (appData.icsActive) {
13970         SendToICS(ics_prefix);
13971         SendToICS("flag\n");
13972     } else {
13973         switch (gameMode) {
13974           default:
13975             return;
13976           case MachinePlaysWhite:
13977             if (whiteFlag) {
13978                 if (blackFlag)
13979                   GameEnds(GameIsDrawn, "Both players ran out of time",
13980                            GE_PLAYER);
13981                 else
13982                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13983             } else {
13984                 DisplayError(_("Your opponent is not out of time"), 0);
13985             }
13986             break;
13987           case MachinePlaysBlack:
13988             if (blackFlag) {
13989                 if (whiteFlag)
13990                   GameEnds(GameIsDrawn, "Both players ran out of time",
13991                            GE_PLAYER);
13992                 else
13993                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13994             } else {
13995                 DisplayError(_("Your opponent is not out of time"), 0);
13996             }
13997             break;
13998         }
13999     }
14000 }
14001
14002 void
14003 ClockClick (int which)
14004 {       // [HGM] code moved to back-end from winboard.c
14005         if(which) { // black clock
14006           if (gameMode == EditPosition || gameMode == IcsExamining) {
14007             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14008             SetBlackToPlayEvent();
14009           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14010           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14011           } else if (shiftKey) {
14012             AdjustClock(which, -1);
14013           } else if (gameMode == IcsPlayingWhite ||
14014                      gameMode == MachinePlaysBlack) {
14015             CallFlagEvent();
14016           }
14017         } else { // white clock
14018           if (gameMode == EditPosition || gameMode == IcsExamining) {
14019             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14020             SetWhiteToPlayEvent();
14021           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14022           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14023           } else if (shiftKey) {
14024             AdjustClock(which, -1);
14025           } else if (gameMode == IcsPlayingBlack ||
14026                    gameMode == MachinePlaysWhite) {
14027             CallFlagEvent();
14028           }
14029         }
14030 }
14031
14032 void
14033 DrawEvent ()
14034 {
14035     /* Offer draw or accept pending draw offer from opponent */
14036
14037     if (appData.icsActive) {
14038         /* Note: tournament rules require draw offers to be
14039            made after you make your move but before you punch
14040            your clock.  Currently ICS doesn't let you do that;
14041            instead, you immediately punch your clock after making
14042            a move, but you can offer a draw at any time. */
14043
14044         SendToICS(ics_prefix);
14045         SendToICS("draw\n");
14046         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14047     } else if (cmailMsgLoaded) {
14048         if (currentMove == cmailOldMove &&
14049             commentList[cmailOldMove] != NULL &&
14050             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14051                    "Black offers a draw" : "White offers a draw")) {
14052             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14053             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14054         } else if (currentMove == cmailOldMove + 1) {
14055             char *offer = WhiteOnMove(cmailOldMove) ?
14056               "White offers a draw" : "Black offers a draw";
14057             AppendComment(currentMove, offer, TRUE);
14058             DisplayComment(currentMove - 1, offer);
14059             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14060         } else {
14061             DisplayError(_("You must make your move before offering a draw"), 0);
14062             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14063         }
14064     } else if (first.offeredDraw) {
14065         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14066     } else {
14067         if (first.sendDrawOffers) {
14068             SendToProgram("draw\n", &first);
14069             userOfferedDraw = TRUE;
14070         }
14071     }
14072 }
14073
14074 void
14075 AdjournEvent ()
14076 {
14077     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14078
14079     if (appData.icsActive) {
14080         SendToICS(ics_prefix);
14081         SendToICS("adjourn\n");
14082     } else {
14083         /* Currently GNU Chess doesn't offer or accept Adjourns */
14084     }
14085 }
14086
14087
14088 void
14089 AbortEvent ()
14090 {
14091     /* Offer Abort or accept pending Abort offer from opponent */
14092
14093     if (appData.icsActive) {
14094         SendToICS(ics_prefix);
14095         SendToICS("abort\n");
14096     } else {
14097         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14098     }
14099 }
14100
14101 void
14102 ResignEvent ()
14103 {
14104     /* Resign.  You can do this even if it's not your turn. */
14105
14106     if (appData.icsActive) {
14107         SendToICS(ics_prefix);
14108         SendToICS("resign\n");
14109     } else {
14110         switch (gameMode) {
14111           case MachinePlaysWhite:
14112             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14113             break;
14114           case MachinePlaysBlack:
14115             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14116             break;
14117           case EditGame:
14118             if (cmailMsgLoaded) {
14119                 TruncateGame();
14120                 if (WhiteOnMove(cmailOldMove)) {
14121                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14122                 } else {
14123                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14124                 }
14125                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14126             }
14127             break;
14128           default:
14129             break;
14130         }
14131     }
14132 }
14133
14134
14135 void
14136 StopObservingEvent ()
14137 {
14138     /* Stop observing current games */
14139     SendToICS(ics_prefix);
14140     SendToICS("unobserve\n");
14141 }
14142
14143 void
14144 StopExaminingEvent ()
14145 {
14146     /* Stop observing current game */
14147     SendToICS(ics_prefix);
14148     SendToICS("unexamine\n");
14149 }
14150
14151 void
14152 ForwardInner (int target)
14153 {
14154     int limit;
14155
14156     if (appData.debugMode)
14157         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14158                 target, currentMove, forwardMostMove);
14159
14160     if (gameMode == EditPosition)
14161       return;
14162
14163     MarkTargetSquares(1);
14164
14165     if (gameMode == PlayFromGameFile && !pausing)
14166       PauseEvent();
14167
14168     if (gameMode == IcsExamining && pausing)
14169       limit = pauseExamForwardMostMove;
14170     else
14171       limit = forwardMostMove;
14172
14173     if (target > limit) target = limit;
14174
14175     if (target > 0 && moveList[target - 1][0]) {
14176         int fromX, fromY, toX, toY;
14177         toX = moveList[target - 1][2] - AAA;
14178         toY = moveList[target - 1][3] - ONE;
14179         if (moveList[target - 1][1] == '@') {
14180             if (appData.highlightLastMove) {
14181                 SetHighlights(-1, -1, toX, toY);
14182             }
14183         } else {
14184             fromX = moveList[target - 1][0] - AAA;
14185             fromY = moveList[target - 1][1] - ONE;
14186             if (target == currentMove + 1) {
14187                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14188             }
14189             if (appData.highlightLastMove) {
14190                 SetHighlights(fromX, fromY, toX, toY);
14191             }
14192         }
14193     }
14194     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14195         gameMode == Training || gameMode == PlayFromGameFile ||
14196         gameMode == AnalyzeFile) {
14197         while (currentMove < target) {
14198             SendMoveToProgram(currentMove++, &first);
14199         }
14200     } else {
14201         currentMove = target;
14202     }
14203
14204     if (gameMode == EditGame || gameMode == EndOfGame) {
14205         whiteTimeRemaining = timeRemaining[0][currentMove];
14206         blackTimeRemaining = timeRemaining[1][currentMove];
14207     }
14208     DisplayBothClocks();
14209     DisplayMove(currentMove - 1);
14210     DrawPosition(FALSE, boards[currentMove]);
14211     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14212     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14213         DisplayComment(currentMove - 1, commentList[currentMove]);
14214     }
14215 }
14216
14217
14218 void
14219 ForwardEvent ()
14220 {
14221     if (gameMode == IcsExamining && !pausing) {
14222         SendToICS(ics_prefix);
14223         SendToICS("forward\n");
14224     } else {
14225         ForwardInner(currentMove + 1);
14226     }
14227 }
14228
14229 void
14230 ToEndEvent ()
14231 {
14232     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14233         /* to optimze, we temporarily turn off analysis mode while we feed
14234          * the remaining moves to the engine. Otherwise we get analysis output
14235          * after each move.
14236          */
14237         if (first.analysisSupport) {
14238           SendToProgram("exit\nforce\n", &first);
14239           first.analyzing = FALSE;
14240         }
14241     }
14242
14243     if (gameMode == IcsExamining && !pausing) {
14244         SendToICS(ics_prefix);
14245         SendToICS("forward 999999\n");
14246     } else {
14247         ForwardInner(forwardMostMove);
14248     }
14249
14250     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14251         /* we have fed all the moves, so reactivate analysis mode */
14252         SendToProgram("analyze\n", &first);
14253         first.analyzing = TRUE;
14254         /*first.maybeThinking = TRUE;*/
14255         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14256     }
14257 }
14258
14259 void
14260 BackwardInner (int target)
14261 {
14262     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14263
14264     if (appData.debugMode)
14265         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14266                 target, currentMove, forwardMostMove);
14267
14268     if (gameMode == EditPosition) return;
14269     MarkTargetSquares(1);
14270     if (currentMove <= backwardMostMove) {
14271         ClearHighlights();
14272         DrawPosition(full_redraw, boards[currentMove]);
14273         return;
14274     }
14275     if (gameMode == PlayFromGameFile && !pausing)
14276       PauseEvent();
14277
14278     if (moveList[target][0]) {
14279         int fromX, fromY, toX, toY;
14280         toX = moveList[target][2] - AAA;
14281         toY = moveList[target][3] - ONE;
14282         if (moveList[target][1] == '@') {
14283             if (appData.highlightLastMove) {
14284                 SetHighlights(-1, -1, toX, toY);
14285             }
14286         } else {
14287             fromX = moveList[target][0] - AAA;
14288             fromY = moveList[target][1] - ONE;
14289             if (target == currentMove - 1) {
14290                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14291             }
14292             if (appData.highlightLastMove) {
14293                 SetHighlights(fromX, fromY, toX, toY);
14294             }
14295         }
14296     }
14297     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14298         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14299         while (currentMove > target) {
14300             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14301                 // null move cannot be undone. Reload program with move history before it.
14302                 int i;
14303                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14304                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14305                 }
14306                 SendBoard(&first, i); 
14307                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14308                 break;
14309             }
14310             SendToProgram("undo\n", &first);
14311             currentMove--;
14312         }
14313     } else {
14314         currentMove = target;
14315     }
14316
14317     if (gameMode == EditGame || gameMode == EndOfGame) {
14318         whiteTimeRemaining = timeRemaining[0][currentMove];
14319         blackTimeRemaining = timeRemaining[1][currentMove];
14320     }
14321     DisplayBothClocks();
14322     DisplayMove(currentMove - 1);
14323     DrawPosition(full_redraw, boards[currentMove]);
14324     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14325     // [HGM] PV info: routine tests if comment empty
14326     DisplayComment(currentMove - 1, commentList[currentMove]);
14327 }
14328
14329 void
14330 BackwardEvent ()
14331 {
14332     if (gameMode == IcsExamining && !pausing) {
14333         SendToICS(ics_prefix);
14334         SendToICS("backward\n");
14335     } else {
14336         BackwardInner(currentMove - 1);
14337     }
14338 }
14339
14340 void
14341 ToStartEvent ()
14342 {
14343     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14344         /* to optimize, we temporarily turn off analysis mode while we undo
14345          * all the moves. Otherwise we get analysis output after each undo.
14346          */
14347         if (first.analysisSupport) {
14348           SendToProgram("exit\nforce\n", &first);
14349           first.analyzing = FALSE;
14350         }
14351     }
14352
14353     if (gameMode == IcsExamining && !pausing) {
14354         SendToICS(ics_prefix);
14355         SendToICS("backward 999999\n");
14356     } else {
14357         BackwardInner(backwardMostMove);
14358     }
14359
14360     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14361         /* we have fed all the moves, so reactivate analysis mode */
14362         SendToProgram("analyze\n", &first);
14363         first.analyzing = TRUE;
14364         /*first.maybeThinking = TRUE;*/
14365         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14366     }
14367 }
14368
14369 void
14370 ToNrEvent (int to)
14371 {
14372   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14373   if (to >= forwardMostMove) to = forwardMostMove;
14374   if (to <= backwardMostMove) to = backwardMostMove;
14375   if (to < currentMove) {
14376     BackwardInner(to);
14377   } else {
14378     ForwardInner(to);
14379   }
14380 }
14381
14382 void
14383 RevertEvent (Boolean annotate)
14384 {
14385     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14386         return;
14387     }
14388     if (gameMode != IcsExamining) {
14389         DisplayError(_("You are not examining a game"), 0);
14390         return;
14391     }
14392     if (pausing) {
14393         DisplayError(_("You can't revert while pausing"), 0);
14394         return;
14395     }
14396     SendToICS(ics_prefix);
14397     SendToICS("revert\n");
14398 }
14399
14400 void
14401 RetractMoveEvent ()
14402 {
14403     switch (gameMode) {
14404       case MachinePlaysWhite:
14405       case MachinePlaysBlack:
14406         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14407             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14408             return;
14409         }
14410         if (forwardMostMove < 2) return;
14411         currentMove = forwardMostMove = forwardMostMove - 2;
14412         whiteTimeRemaining = timeRemaining[0][currentMove];
14413         blackTimeRemaining = timeRemaining[1][currentMove];
14414         DisplayBothClocks();
14415         DisplayMove(currentMove - 1);
14416         ClearHighlights();/*!! could figure this out*/
14417         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14418         SendToProgram("remove\n", &first);
14419         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14420         break;
14421
14422       case BeginningOfGame:
14423       default:
14424         break;
14425
14426       case IcsPlayingWhite:
14427       case IcsPlayingBlack:
14428         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14429             SendToICS(ics_prefix);
14430             SendToICS("takeback 2\n");
14431         } else {
14432             SendToICS(ics_prefix);
14433             SendToICS("takeback 1\n");
14434         }
14435         break;
14436     }
14437 }
14438
14439 void
14440 MoveNowEvent ()
14441 {
14442     ChessProgramState *cps;
14443
14444     switch (gameMode) {
14445       case MachinePlaysWhite:
14446         if (!WhiteOnMove(forwardMostMove)) {
14447             DisplayError(_("It is your turn"), 0);
14448             return;
14449         }
14450         cps = &first;
14451         break;
14452       case MachinePlaysBlack:
14453         if (WhiteOnMove(forwardMostMove)) {
14454             DisplayError(_("It is your turn"), 0);
14455             return;
14456         }
14457         cps = &first;
14458         break;
14459       case TwoMachinesPlay:
14460         if (WhiteOnMove(forwardMostMove) ==
14461             (first.twoMachinesColor[0] == 'w')) {
14462             cps = &first;
14463         } else {
14464             cps = &second;
14465         }
14466         break;
14467       case BeginningOfGame:
14468       default:
14469         return;
14470     }
14471     SendToProgram("?\n", cps);
14472 }
14473
14474 void
14475 TruncateGameEvent ()
14476 {
14477     EditGameEvent();
14478     if (gameMode != EditGame) return;
14479     TruncateGame();
14480 }
14481
14482 void
14483 TruncateGame ()
14484 {
14485     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14486     if (forwardMostMove > currentMove) {
14487         if (gameInfo.resultDetails != NULL) {
14488             free(gameInfo.resultDetails);
14489             gameInfo.resultDetails = NULL;
14490             gameInfo.result = GameUnfinished;
14491         }
14492         forwardMostMove = currentMove;
14493         HistorySet(parseList, backwardMostMove, forwardMostMove,
14494                    currentMove-1);
14495     }
14496 }
14497
14498 void
14499 HintEvent ()
14500 {
14501     if (appData.noChessProgram) return;
14502     switch (gameMode) {
14503       case MachinePlaysWhite:
14504         if (WhiteOnMove(forwardMostMove)) {
14505             DisplayError(_("Wait until your turn"), 0);
14506             return;
14507         }
14508         break;
14509       case BeginningOfGame:
14510       case MachinePlaysBlack:
14511         if (!WhiteOnMove(forwardMostMove)) {
14512             DisplayError(_("Wait until your turn"), 0);
14513             return;
14514         }
14515         break;
14516       default:
14517         DisplayError(_("No hint available"), 0);
14518         return;
14519     }
14520     SendToProgram("hint\n", &first);
14521     hintRequested = TRUE;
14522 }
14523
14524 void
14525 BookEvent ()
14526 {
14527     if (appData.noChessProgram) return;
14528     switch (gameMode) {
14529       case MachinePlaysWhite:
14530         if (WhiteOnMove(forwardMostMove)) {
14531             DisplayError(_("Wait until your turn"), 0);
14532             return;
14533         }
14534         break;
14535       case BeginningOfGame:
14536       case MachinePlaysBlack:
14537         if (!WhiteOnMove(forwardMostMove)) {
14538             DisplayError(_("Wait until your turn"), 0);
14539             return;
14540         }
14541         break;
14542       case EditPosition:
14543         EditPositionDone(TRUE);
14544         break;
14545       case TwoMachinesPlay:
14546         return;
14547       default:
14548         break;
14549     }
14550     SendToProgram("bk\n", &first);
14551     bookOutput[0] = NULLCHAR;
14552     bookRequested = TRUE;
14553 }
14554
14555 void
14556 AboutGameEvent ()
14557 {
14558     char *tags = PGNTags(&gameInfo);
14559     TagsPopUp(tags, CmailMsg());
14560     free(tags);
14561 }
14562
14563 /* end button procedures */
14564
14565 void
14566 PrintPosition (FILE *fp, int move)
14567 {
14568     int i, j;
14569
14570     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14571         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14572             char c = PieceToChar(boards[move][i][j]);
14573             fputc(c == 'x' ? '.' : c, fp);
14574             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14575         }
14576     }
14577     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14578       fprintf(fp, "white to play\n");
14579     else
14580       fprintf(fp, "black to play\n");
14581 }
14582
14583 void
14584 PrintOpponents (FILE *fp)
14585 {
14586     if (gameInfo.white != NULL) {
14587         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14588     } else {
14589         fprintf(fp, "\n");
14590     }
14591 }
14592
14593 /* Find last component of program's own name, using some heuristics */
14594 void
14595 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14596 {
14597     char *p, *q;
14598     int local = (strcmp(host, "localhost") == 0);
14599     while (!local && (p = strchr(prog, ';')) != NULL) {
14600         p++;
14601         while (*p == ' ') p++;
14602         prog = p;
14603     }
14604     if (*prog == '"' || *prog == '\'') {
14605         q = strchr(prog + 1, *prog);
14606     } else {
14607         q = strchr(prog, ' ');
14608     }
14609     if (q == NULL) q = prog + strlen(prog);
14610     p = q;
14611     while (p >= prog && *p != '/' && *p != '\\') p--;
14612     p++;
14613     if(p == prog && *p == '"') p++;
14614     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14615     memcpy(buf, p, q - p);
14616     buf[q - p] = NULLCHAR;
14617     if (!local) {
14618         strcat(buf, "@");
14619         strcat(buf, host);
14620     }
14621 }
14622
14623 char *
14624 TimeControlTagValue ()
14625 {
14626     char buf[MSG_SIZ];
14627     if (!appData.clockMode) {
14628       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14629     } else if (movesPerSession > 0) {
14630       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14631     } else if (timeIncrement == 0) {
14632       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14633     } else {
14634       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14635     }
14636     return StrSave(buf);
14637 }
14638
14639 void
14640 SetGameInfo ()
14641 {
14642     /* This routine is used only for certain modes */
14643     VariantClass v = gameInfo.variant;
14644     ChessMove r = GameUnfinished;
14645     char *p = NULL;
14646
14647     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14648         r = gameInfo.result;
14649         p = gameInfo.resultDetails;
14650         gameInfo.resultDetails = NULL;
14651     }
14652     ClearGameInfo(&gameInfo);
14653     gameInfo.variant = v;
14654
14655     switch (gameMode) {
14656       case MachinePlaysWhite:
14657         gameInfo.event = StrSave( appData.pgnEventHeader );
14658         gameInfo.site = StrSave(HostName());
14659         gameInfo.date = PGNDate();
14660         gameInfo.round = StrSave("-");
14661         gameInfo.white = StrSave(first.tidy);
14662         gameInfo.black = StrSave(UserName());
14663         gameInfo.timeControl = TimeControlTagValue();
14664         break;
14665
14666       case MachinePlaysBlack:
14667         gameInfo.event = StrSave( appData.pgnEventHeader );
14668         gameInfo.site = StrSave(HostName());
14669         gameInfo.date = PGNDate();
14670         gameInfo.round = StrSave("-");
14671         gameInfo.white = StrSave(UserName());
14672         gameInfo.black = StrSave(first.tidy);
14673         gameInfo.timeControl = TimeControlTagValue();
14674         break;
14675
14676       case TwoMachinesPlay:
14677         gameInfo.event = StrSave( appData.pgnEventHeader );
14678         gameInfo.site = StrSave(HostName());
14679         gameInfo.date = PGNDate();
14680         if (roundNr > 0) {
14681             char buf[MSG_SIZ];
14682             snprintf(buf, MSG_SIZ, "%d", roundNr);
14683             gameInfo.round = StrSave(buf);
14684         } else {
14685             gameInfo.round = StrSave("-");
14686         }
14687         if (first.twoMachinesColor[0] == 'w') {
14688             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14689             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14690         } else {
14691             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14692             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14693         }
14694         gameInfo.timeControl = TimeControlTagValue();
14695         break;
14696
14697       case EditGame:
14698         gameInfo.event = StrSave("Edited game");
14699         gameInfo.site = StrSave(HostName());
14700         gameInfo.date = PGNDate();
14701         gameInfo.round = StrSave("-");
14702         gameInfo.white = StrSave("-");
14703         gameInfo.black = StrSave("-");
14704         gameInfo.result = r;
14705         gameInfo.resultDetails = p;
14706         break;
14707
14708       case EditPosition:
14709         gameInfo.event = StrSave("Edited position");
14710         gameInfo.site = StrSave(HostName());
14711         gameInfo.date = PGNDate();
14712         gameInfo.round = StrSave("-");
14713         gameInfo.white = StrSave("-");
14714         gameInfo.black = StrSave("-");
14715         break;
14716
14717       case IcsPlayingWhite:
14718       case IcsPlayingBlack:
14719       case IcsObserving:
14720       case IcsExamining:
14721         break;
14722
14723       case PlayFromGameFile:
14724         gameInfo.event = StrSave("Game from non-PGN file");
14725         gameInfo.site = StrSave(HostName());
14726         gameInfo.date = PGNDate();
14727         gameInfo.round = StrSave("-");
14728         gameInfo.white = StrSave("?");
14729         gameInfo.black = StrSave("?");
14730         break;
14731
14732       default:
14733         break;
14734     }
14735 }
14736
14737 void
14738 ReplaceComment (int index, char *text)
14739 {
14740     int len;
14741     char *p;
14742     float score;
14743
14744     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14745        pvInfoList[index-1].depth == len &&
14746        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14747        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14748     while (*text == '\n') text++;
14749     len = strlen(text);
14750     while (len > 0 && text[len - 1] == '\n') len--;
14751
14752     if (commentList[index] != NULL)
14753       free(commentList[index]);
14754
14755     if (len == 0) {
14756         commentList[index] = NULL;
14757         return;
14758     }
14759   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14760       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14761       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14762     commentList[index] = (char *) malloc(len + 2);
14763     strncpy(commentList[index], text, len);
14764     commentList[index][len] = '\n';
14765     commentList[index][len + 1] = NULLCHAR;
14766   } else {
14767     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14768     char *p;
14769     commentList[index] = (char *) malloc(len + 7);
14770     safeStrCpy(commentList[index], "{\n", 3);
14771     safeStrCpy(commentList[index]+2, text, len+1);
14772     commentList[index][len+2] = NULLCHAR;
14773     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14774     strcat(commentList[index], "\n}\n");
14775   }
14776 }
14777
14778 void
14779 CrushCRs (char *text)
14780 {
14781   char *p = text;
14782   char *q = text;
14783   char ch;
14784
14785   do {
14786     ch = *p++;
14787     if (ch == '\r') continue;
14788     *q++ = ch;
14789   } while (ch != '\0');
14790 }
14791
14792 void
14793 AppendComment (int index, char *text, Boolean addBraces)
14794 /* addBraces  tells if we should add {} */
14795 {
14796     int oldlen, len;
14797     char *old;
14798
14799 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14800     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14801
14802     CrushCRs(text);
14803     while (*text == '\n') text++;
14804     len = strlen(text);
14805     while (len > 0 && text[len - 1] == '\n') len--;
14806     text[len] = NULLCHAR;
14807
14808     if (len == 0) return;
14809
14810     if (commentList[index] != NULL) {
14811       Boolean addClosingBrace = addBraces;
14812         old = commentList[index];
14813         oldlen = strlen(old);
14814         while(commentList[index][oldlen-1] ==  '\n')
14815           commentList[index][--oldlen] = NULLCHAR;
14816         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14817         safeStrCpy(commentList[index], old, oldlen + len + 6);
14818         free(old);
14819         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14820         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14821           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14822           while (*text == '\n') { text++; len--; }
14823           commentList[index][--oldlen] = NULLCHAR;
14824       }
14825         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14826         else          strcat(commentList[index], "\n");
14827         strcat(commentList[index], text);
14828         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14829         else          strcat(commentList[index], "\n");
14830     } else {
14831         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14832         if(addBraces)
14833           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14834         else commentList[index][0] = NULLCHAR;
14835         strcat(commentList[index], text);
14836         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14837         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14838     }
14839 }
14840
14841 static char *
14842 FindStr (char * text, char * sub_text)
14843 {
14844     char * result = strstr( text, sub_text );
14845
14846     if( result != NULL ) {
14847         result += strlen( sub_text );
14848     }
14849
14850     return result;
14851 }
14852
14853 /* [AS] Try to extract PV info from PGN comment */
14854 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14855 char *
14856 GetInfoFromComment (int index, char * text)
14857 {
14858     char * sep = text, *p;
14859
14860     if( text != NULL && index > 0 ) {
14861         int score = 0;
14862         int depth = 0;
14863         int time = -1, sec = 0, deci;
14864         char * s_eval = FindStr( text, "[%eval " );
14865         char * s_emt = FindStr( text, "[%emt " );
14866
14867         if( s_eval != NULL || s_emt != NULL ) {
14868             /* New style */
14869             char delim;
14870
14871             if( s_eval != NULL ) {
14872                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14873                     return text;
14874                 }
14875
14876                 if( delim != ']' ) {
14877                     return text;
14878                 }
14879             }
14880
14881             if( s_emt != NULL ) {
14882             }
14883                 return text;
14884         }
14885         else {
14886             /* We expect something like: [+|-]nnn.nn/dd */
14887             int score_lo = 0;
14888
14889             if(*text != '{') return text; // [HGM] braces: must be normal comment
14890
14891             sep = strchr( text, '/' );
14892             if( sep == NULL || sep < (text+4) ) {
14893                 return text;
14894             }
14895
14896             p = text;
14897             if(p[1] == '(') { // comment starts with PV
14898                p = strchr(p, ')'); // locate end of PV
14899                if(p == NULL || sep < p+5) return text;
14900                // at this point we have something like "{(.*) +0.23/6 ..."
14901                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14902                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14903                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14904             }
14905             time = -1; sec = -1; deci = -1;
14906             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14907                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14908                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14909                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14910                 return text;
14911             }
14912
14913             if( score_lo < 0 || score_lo >= 100 ) {
14914                 return text;
14915             }
14916
14917             if(sec >= 0) time = 600*time + 10*sec; else
14918             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14919
14920             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14921
14922             /* [HGM] PV time: now locate end of PV info */
14923             while( *++sep >= '0' && *sep <= '9'); // strip depth
14924             if(time >= 0)
14925             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14926             if(sec >= 0)
14927             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14928             if(deci >= 0)
14929             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14930             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
14931         }
14932
14933         if( depth <= 0 ) {
14934             return text;
14935         }
14936
14937         if( time < 0 ) {
14938             time = -1;
14939         }
14940
14941         pvInfoList[index-1].depth = depth;
14942         pvInfoList[index-1].score = score;
14943         pvInfoList[index-1].time  = 10*time; // centi-sec
14944         if(*sep == '}') *sep = 0; else *--sep = '{';
14945         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14946     }
14947     return sep;
14948 }
14949
14950 void
14951 SendToProgram (char *message, ChessProgramState *cps)
14952 {
14953     int count, outCount, error;
14954     char buf[MSG_SIZ];
14955
14956     if (cps->pr == NoProc) return;
14957     Attention(cps);
14958
14959     if (appData.debugMode) {
14960         TimeMark now;
14961         GetTimeMark(&now);
14962         fprintf(debugFP, "%ld >%-6s: %s",
14963                 SubtractTimeMarks(&now, &programStartTime),
14964                 cps->which, message);
14965     }
14966
14967     count = strlen(message);
14968     outCount = OutputToProcess(cps->pr, message, count, &error);
14969     if (outCount < count && !exiting
14970                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14971       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14972       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14973         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14974             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14975                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14976                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14977                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14978             } else {
14979                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14980                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14981                 gameInfo.result = res;
14982             }
14983             gameInfo.resultDetails = StrSave(buf);
14984         }
14985         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14986         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14987     }
14988 }
14989
14990 void
14991 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
14992 {
14993     char *end_str;
14994     char buf[MSG_SIZ];
14995     ChessProgramState *cps = (ChessProgramState *)closure;
14996
14997     if (isr != cps->isr) return; /* Killed intentionally */
14998     if (count <= 0) {
14999         if (count == 0) {
15000             RemoveInputSource(cps->isr);
15001             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15002             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15003                     _(cps->which), cps->program);
15004         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15005                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15006                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15007                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15008                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15009                 } else {
15010                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15011                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15012                     gameInfo.result = res;
15013                 }
15014                 gameInfo.resultDetails = StrSave(buf);
15015             }
15016             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15017             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15018         } else {
15019             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15020                     _(cps->which), cps->program);
15021             RemoveInputSource(cps->isr);
15022
15023             /* [AS] Program is misbehaving badly... kill it */
15024             if( count == -2 ) {
15025                 DestroyChildProcess( cps->pr, 9 );
15026                 cps->pr = NoProc;
15027             }
15028
15029             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15030         }
15031         return;
15032     }
15033
15034     if ((end_str = strchr(message, '\r')) != NULL)
15035       *end_str = NULLCHAR;
15036     if ((end_str = strchr(message, '\n')) != NULL)
15037       *end_str = NULLCHAR;
15038
15039     if (appData.debugMode) {
15040         TimeMark now; int print = 1;
15041         char *quote = ""; char c; int i;
15042
15043         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15044                 char start = message[0];
15045                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15046                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15047                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15048                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15049                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15050                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15051                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15052                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15053                    sscanf(message, "hint: %c", &c)!=1 && 
15054                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15055                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15056                     print = (appData.engineComments >= 2);
15057                 }
15058                 message[0] = start; // restore original message
15059         }
15060         if(print) {
15061                 GetTimeMark(&now);
15062                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15063                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15064                         quote,
15065                         message);
15066         }
15067     }
15068
15069     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15070     if (appData.icsEngineAnalyze) {
15071         if (strstr(message, "whisper") != NULL ||
15072              strstr(message, "kibitz") != NULL ||
15073             strstr(message, "tellics") != NULL) return;
15074     }
15075
15076     HandleMachineMove(message, cps);
15077 }
15078
15079
15080 void
15081 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15082 {
15083     char buf[MSG_SIZ];
15084     int seconds;
15085
15086     if( timeControl_2 > 0 ) {
15087         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15088             tc = timeControl_2;
15089         }
15090     }
15091     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15092     inc /= cps->timeOdds;
15093     st  /= cps->timeOdds;
15094
15095     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15096
15097     if (st > 0) {
15098       /* Set exact time per move, normally using st command */
15099       if (cps->stKludge) {
15100         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15101         seconds = st % 60;
15102         if (seconds == 0) {
15103           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15104         } else {
15105           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15106         }
15107       } else {
15108         snprintf(buf, MSG_SIZ, "st %d\n", st);
15109       }
15110     } else {
15111       /* Set conventional or incremental time control, using level command */
15112       if (seconds == 0) {
15113         /* Note old gnuchess bug -- minutes:seconds used to not work.
15114            Fixed in later versions, but still avoid :seconds
15115            when seconds is 0. */
15116         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15117       } else {
15118         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15119                  seconds, inc/1000.);
15120       }
15121     }
15122     SendToProgram(buf, cps);
15123
15124     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15125     /* Orthogonally, limit search to given depth */
15126     if (sd > 0) {
15127       if (cps->sdKludge) {
15128         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15129       } else {
15130         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15131       }
15132       SendToProgram(buf, cps);
15133     }
15134
15135     if(cps->nps >= 0) { /* [HGM] nps */
15136         if(cps->supportsNPS == FALSE)
15137           cps->nps = -1; // don't use if engine explicitly says not supported!
15138         else {
15139           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15140           SendToProgram(buf, cps);
15141         }
15142     }
15143 }
15144
15145 ChessProgramState *
15146 WhitePlayer ()
15147 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15148 {
15149     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15150        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15151         return &second;
15152     return &first;
15153 }
15154
15155 void
15156 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15157 {
15158     char message[MSG_SIZ];
15159     long time, otime;
15160
15161     /* Note: this routine must be called when the clocks are stopped
15162        or when they have *just* been set or switched; otherwise
15163        it will be off by the time since the current tick started.
15164     */
15165     if (machineWhite) {
15166         time = whiteTimeRemaining / 10;
15167         otime = blackTimeRemaining / 10;
15168     } else {
15169         time = blackTimeRemaining / 10;
15170         otime = whiteTimeRemaining / 10;
15171     }
15172     /* [HGM] translate opponent's time by time-odds factor */
15173     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15174     if (appData.debugMode) {
15175         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15176     }
15177
15178     if (time <= 0) time = 1;
15179     if (otime <= 0) otime = 1;
15180
15181     snprintf(message, MSG_SIZ, "time %ld\n", time);
15182     SendToProgram(message, cps);
15183
15184     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15185     SendToProgram(message, cps);
15186 }
15187
15188 int
15189 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15190 {
15191   char buf[MSG_SIZ];
15192   int len = strlen(name);
15193   int val;
15194
15195   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15196     (*p) += len + 1;
15197     sscanf(*p, "%d", &val);
15198     *loc = (val != 0);
15199     while (**p && **p != ' ')
15200       (*p)++;
15201     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15202     SendToProgram(buf, cps);
15203     return TRUE;
15204   }
15205   return FALSE;
15206 }
15207
15208 int
15209 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15210 {
15211   char buf[MSG_SIZ];
15212   int len = strlen(name);
15213   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15214     (*p) += len + 1;
15215     sscanf(*p, "%d", loc);
15216     while (**p && **p != ' ') (*p)++;
15217     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15218     SendToProgram(buf, cps);
15219     return TRUE;
15220   }
15221   return FALSE;
15222 }
15223
15224 int
15225 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15226 {
15227   char buf[MSG_SIZ];
15228   int len = strlen(name);
15229   if (strncmp((*p), name, len) == 0
15230       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15231     (*p) += len + 2;
15232     sscanf(*p, "%[^\"]", loc);
15233     while (**p && **p != '\"') (*p)++;
15234     if (**p == '\"') (*p)++;
15235     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15236     SendToProgram(buf, cps);
15237     return TRUE;
15238   }
15239   return FALSE;
15240 }
15241
15242 int
15243 ParseOption (Option *opt, ChessProgramState *cps)
15244 // [HGM] options: process the string that defines an engine option, and determine
15245 // name, type, default value, and allowed value range
15246 {
15247         char *p, *q, buf[MSG_SIZ];
15248         int n, min = (-1)<<31, max = 1<<31, def;
15249
15250         if(p = strstr(opt->name, " -spin ")) {
15251             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15252             if(max < min) max = min; // enforce consistency
15253             if(def < min) def = min;
15254             if(def > max) def = max;
15255             opt->value = def;
15256             opt->min = min;
15257             opt->max = max;
15258             opt->type = Spin;
15259         } else if((p = strstr(opt->name, " -slider "))) {
15260             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15261             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15262             if(max < min) max = min; // enforce consistency
15263             if(def < min) def = min;
15264             if(def > max) def = max;
15265             opt->value = def;
15266             opt->min = min;
15267             opt->max = max;
15268             opt->type = Spin; // Slider;
15269         } else if((p = strstr(opt->name, " -string "))) {
15270             opt->textValue = p+9;
15271             opt->type = TextBox;
15272         } else if((p = strstr(opt->name, " -file "))) {
15273             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15274             opt->textValue = p+7;
15275             opt->type = FileName; // FileName;
15276         } else if((p = strstr(opt->name, " -path "))) {
15277             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15278             opt->textValue = p+7;
15279             opt->type = PathName; // PathName;
15280         } else if(p = strstr(opt->name, " -check ")) {
15281             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15282             opt->value = (def != 0);
15283             opt->type = CheckBox;
15284         } else if(p = strstr(opt->name, " -combo ")) {
15285             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15286             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15287             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15288             opt->value = n = 0;
15289             while(q = StrStr(q, " /// ")) {
15290                 n++; *q = 0;    // count choices, and null-terminate each of them
15291                 q += 5;
15292                 if(*q == '*') { // remember default, which is marked with * prefix
15293                     q++;
15294                     opt->value = n;
15295                 }
15296                 cps->comboList[cps->comboCnt++] = q;
15297             }
15298             cps->comboList[cps->comboCnt++] = NULL;
15299             opt->max = n + 1;
15300             opt->type = ComboBox;
15301         } else if(p = strstr(opt->name, " -button")) {
15302             opt->type = Button;
15303         } else if(p = strstr(opt->name, " -save")) {
15304             opt->type = SaveButton;
15305         } else return FALSE;
15306         *p = 0; // terminate option name
15307         // now look if the command-line options define a setting for this engine option.
15308         if(cps->optionSettings && cps->optionSettings[0])
15309             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15310         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15311           snprintf(buf, MSG_SIZ, "option %s", p);
15312                 if(p = strstr(buf, ",")) *p = 0;
15313                 if(q = strchr(buf, '=')) switch(opt->type) {
15314                     case ComboBox:
15315                         for(n=0; n<opt->max; n++)
15316                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15317                         break;
15318                     case TextBox:
15319                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15320                         break;
15321                     case Spin:
15322                     case CheckBox:
15323                         opt->value = atoi(q+1);
15324                     default:
15325                         break;
15326                 }
15327                 strcat(buf, "\n");
15328                 SendToProgram(buf, cps);
15329         }
15330         return TRUE;
15331 }
15332
15333 void
15334 FeatureDone (ChessProgramState *cps, int val)
15335 {
15336   DelayedEventCallback cb = GetDelayedEvent();
15337   if ((cb == InitBackEnd3 && cps == &first) ||
15338       (cb == SettingsMenuIfReady && cps == &second) ||
15339       (cb == LoadEngine) ||
15340       (cb == TwoMachinesEventIfReady)) {
15341     CancelDelayedEvent();
15342     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15343   }
15344   cps->initDone = val;
15345 }
15346
15347 /* Parse feature command from engine */
15348 void
15349 ParseFeatures (char *args, ChessProgramState *cps)
15350 {
15351   char *p = args;
15352   char *q;
15353   int val;
15354   char buf[MSG_SIZ];
15355
15356   for (;;) {
15357     while (*p == ' ') p++;
15358     if (*p == NULLCHAR) return;
15359
15360     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15361     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15362     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15363     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15364     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15365     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15366     if (BoolFeature(&p, "reuse", &val, cps)) {
15367       /* Engine can disable reuse, but can't enable it if user said no */
15368       if (!val) cps->reuse = FALSE;
15369       continue;
15370     }
15371     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15372     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15373       if (gameMode == TwoMachinesPlay) {
15374         DisplayTwoMachinesTitle();
15375       } else {
15376         DisplayTitle("");
15377       }
15378       continue;
15379     }
15380     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15381     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15382     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15383     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15384     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15385     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15386     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15387     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15388     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15389     if (IntFeature(&p, "done", &val, cps)) {
15390       FeatureDone(cps, val);
15391       continue;
15392     }
15393     /* Added by Tord: */
15394     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15395     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15396     /* End of additions by Tord */
15397
15398     /* [HGM] added features: */
15399     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15400     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15401     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15402     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15403     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15404     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15405     if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15406         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15407           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15408             SendToProgram(buf, cps);
15409             continue;
15410         }
15411         if(cps->nrOptions >= MAX_OPTIONS) {
15412             cps->nrOptions--;
15413             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15414             DisplayError(buf, 0);
15415         }
15416         continue;
15417     }
15418     /* End of additions by HGM */
15419
15420     /* unknown feature: complain and skip */
15421     q = p;
15422     while (*q && *q != '=') q++;
15423     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15424     SendToProgram(buf, cps);
15425     p = q;
15426     if (*p == '=') {
15427       p++;
15428       if (*p == '\"') {
15429         p++;
15430         while (*p && *p != '\"') p++;
15431         if (*p == '\"') p++;
15432       } else {
15433         while (*p && *p != ' ') p++;
15434       }
15435     }
15436   }
15437
15438 }
15439
15440 void
15441 PeriodicUpdatesEvent (int newState)
15442 {
15443     if (newState == appData.periodicUpdates)
15444       return;
15445
15446     appData.periodicUpdates=newState;
15447
15448     /* Display type changes, so update it now */
15449 //    DisplayAnalysis();
15450
15451     /* Get the ball rolling again... */
15452     if (newState) {
15453         AnalysisPeriodicEvent(1);
15454         StartAnalysisClock();
15455     }
15456 }
15457
15458 void
15459 PonderNextMoveEvent (int newState)
15460 {
15461     if (newState == appData.ponderNextMove) return;
15462     if (gameMode == EditPosition) EditPositionDone(TRUE);
15463     if (newState) {
15464         SendToProgram("hard\n", &first);
15465         if (gameMode == TwoMachinesPlay) {
15466             SendToProgram("hard\n", &second);
15467         }
15468     } else {
15469         SendToProgram("easy\n", &first);
15470         thinkOutput[0] = NULLCHAR;
15471         if (gameMode == TwoMachinesPlay) {
15472             SendToProgram("easy\n", &second);
15473         }
15474     }
15475     appData.ponderNextMove = newState;
15476 }
15477
15478 void
15479 NewSettingEvent (int option, int *feature, char *command, int value)
15480 {
15481     char buf[MSG_SIZ];
15482
15483     if (gameMode == EditPosition) EditPositionDone(TRUE);
15484     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15485     if(feature == NULL || *feature) SendToProgram(buf, &first);
15486     if (gameMode == TwoMachinesPlay) {
15487         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15488     }
15489 }
15490
15491 void
15492 ShowThinkingEvent ()
15493 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15494 {
15495     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15496     int newState = appData.showThinking
15497         // [HGM] thinking: other features now need thinking output as well
15498         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15499
15500     if (oldState == newState) return;
15501     oldState = newState;
15502     if (gameMode == EditPosition) EditPositionDone(TRUE);
15503     if (oldState) {
15504         SendToProgram("post\n", &first);
15505         if (gameMode == TwoMachinesPlay) {
15506             SendToProgram("post\n", &second);
15507         }
15508     } else {
15509         SendToProgram("nopost\n", &first);
15510         thinkOutput[0] = NULLCHAR;
15511         if (gameMode == TwoMachinesPlay) {
15512             SendToProgram("nopost\n", &second);
15513         }
15514     }
15515 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15516 }
15517
15518 void
15519 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15520 {
15521   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15522   if (pr == NoProc) return;
15523   AskQuestion(title, question, replyPrefix, pr);
15524 }
15525
15526 void
15527 TypeInEvent (char firstChar)
15528 {
15529     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15530         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15531         gameMode == AnalyzeMode || gameMode == EditGame || 
15532         gameMode == EditPosition || gameMode == IcsExamining ||
15533         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15534         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15535                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15536                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15537         gameMode == Training) PopUpMoveDialog(firstChar);
15538 }
15539
15540 void
15541 TypeInDoneEvent (char *move)
15542 {
15543         Board board;
15544         int n, fromX, fromY, toX, toY;
15545         char promoChar;
15546         ChessMove moveType;
15547
15548         // [HGM] FENedit
15549         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15550                 EditPositionPasteFEN(move);
15551                 return;
15552         }
15553         // [HGM] movenum: allow move number to be typed in any mode
15554         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15555           ToNrEvent(2*n-1);
15556           return;
15557         }
15558
15559       if (gameMode != EditGame && currentMove != forwardMostMove && 
15560         gameMode != Training) {
15561         DisplayMoveError(_("Displayed move is not current"));
15562       } else {
15563         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15564           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15565         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15566         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15567           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15568           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15569         } else {
15570           DisplayMoveError(_("Could not parse move"));
15571         }
15572       }
15573 }
15574
15575 void
15576 DisplayMove (int moveNumber)
15577 {
15578     char message[MSG_SIZ];
15579     char res[MSG_SIZ];
15580     char cpThinkOutput[MSG_SIZ];
15581
15582     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15583
15584     if (moveNumber == forwardMostMove - 1 ||
15585         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15586
15587         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15588
15589         if (strchr(cpThinkOutput, '\n')) {
15590             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15591         }
15592     } else {
15593         *cpThinkOutput = NULLCHAR;
15594     }
15595
15596     /* [AS] Hide thinking from human user */
15597     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15598         *cpThinkOutput = NULLCHAR;
15599         if( thinkOutput[0] != NULLCHAR ) {
15600             int i;
15601
15602             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15603                 cpThinkOutput[i] = '.';
15604             }
15605             cpThinkOutput[i] = NULLCHAR;
15606             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15607         }
15608     }
15609
15610     if (moveNumber == forwardMostMove - 1 &&
15611         gameInfo.resultDetails != NULL) {
15612         if (gameInfo.resultDetails[0] == NULLCHAR) {
15613           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15614         } else {
15615           snprintf(res, MSG_SIZ, " {%s} %s",
15616                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15617         }
15618     } else {
15619         res[0] = NULLCHAR;
15620     }
15621
15622     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15623         DisplayMessage(res, cpThinkOutput);
15624     } else {
15625       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15626                 WhiteOnMove(moveNumber) ? " " : ".. ",
15627                 parseList[moveNumber], res);
15628         DisplayMessage(message, cpThinkOutput);
15629     }
15630 }
15631
15632 void
15633 DisplayComment (int moveNumber, char *text)
15634 {
15635     char title[MSG_SIZ];
15636
15637     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15638       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15639     } else {
15640       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15641               WhiteOnMove(moveNumber) ? " " : ".. ",
15642               parseList[moveNumber]);
15643     }
15644     if (text != NULL && (appData.autoDisplayComment || commentUp))
15645         CommentPopUp(title, text);
15646 }
15647
15648 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15649  * might be busy thinking or pondering.  It can be omitted if your
15650  * gnuchess is configured to stop thinking immediately on any user
15651  * input.  However, that gnuchess feature depends on the FIONREAD
15652  * ioctl, which does not work properly on some flavors of Unix.
15653  */
15654 void
15655 Attention (ChessProgramState *cps)
15656 {
15657 #if ATTENTION
15658     if (!cps->useSigint) return;
15659     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15660     switch (gameMode) {
15661       case MachinePlaysWhite:
15662       case MachinePlaysBlack:
15663       case TwoMachinesPlay:
15664       case IcsPlayingWhite:
15665       case IcsPlayingBlack:
15666       case AnalyzeMode:
15667       case AnalyzeFile:
15668         /* Skip if we know it isn't thinking */
15669         if (!cps->maybeThinking) return;
15670         if (appData.debugMode)
15671           fprintf(debugFP, "Interrupting %s\n", cps->which);
15672         InterruptChildProcess(cps->pr);
15673         cps->maybeThinking = FALSE;
15674         break;
15675       default:
15676         break;
15677     }
15678 #endif /*ATTENTION*/
15679 }
15680
15681 int
15682 CheckFlags ()
15683 {
15684     if (whiteTimeRemaining <= 0) {
15685         if (!whiteFlag) {
15686             whiteFlag = TRUE;
15687             if (appData.icsActive) {
15688                 if (appData.autoCallFlag &&
15689                     gameMode == IcsPlayingBlack && !blackFlag) {
15690                   SendToICS(ics_prefix);
15691                   SendToICS("flag\n");
15692                 }
15693             } else {
15694                 if (blackFlag) {
15695                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15696                 } else {
15697                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15698                     if (appData.autoCallFlag) {
15699                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15700                         return TRUE;
15701                     }
15702                 }
15703             }
15704         }
15705     }
15706     if (blackTimeRemaining <= 0) {
15707         if (!blackFlag) {
15708             blackFlag = TRUE;
15709             if (appData.icsActive) {
15710                 if (appData.autoCallFlag &&
15711                     gameMode == IcsPlayingWhite && !whiteFlag) {
15712                   SendToICS(ics_prefix);
15713                   SendToICS("flag\n");
15714                 }
15715             } else {
15716                 if (whiteFlag) {
15717                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15718                 } else {
15719                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15720                     if (appData.autoCallFlag) {
15721                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15722                         return TRUE;
15723                     }
15724                 }
15725             }
15726         }
15727     }
15728     return FALSE;
15729 }
15730
15731 void
15732 CheckTimeControl ()
15733 {
15734     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15735         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15736
15737     /*
15738      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15739      */
15740     if ( !WhiteOnMove(forwardMostMove) ) {
15741         /* White made time control */
15742         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15743         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15744         /* [HGM] time odds: correct new time quota for time odds! */
15745                                             / WhitePlayer()->timeOdds;
15746         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15747     } else {
15748         lastBlack -= blackTimeRemaining;
15749         /* Black made time control */
15750         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15751                                             / WhitePlayer()->other->timeOdds;
15752         lastWhite = whiteTimeRemaining;
15753     }
15754 }
15755
15756 void
15757 DisplayBothClocks ()
15758 {
15759     int wom = gameMode == EditPosition ?
15760       !blackPlaysFirst : WhiteOnMove(currentMove);
15761     DisplayWhiteClock(whiteTimeRemaining, wom);
15762     DisplayBlackClock(blackTimeRemaining, !wom);
15763 }
15764
15765
15766 /* Timekeeping seems to be a portability nightmare.  I think everyone
15767    has ftime(), but I'm really not sure, so I'm including some ifdefs
15768    to use other calls if you don't.  Clocks will be less accurate if
15769    you have neither ftime nor gettimeofday.
15770 */
15771
15772 /* VS 2008 requires the #include outside of the function */
15773 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15774 #include <sys/timeb.h>
15775 #endif
15776
15777 /* Get the current time as a TimeMark */
15778 void
15779 GetTimeMark (TimeMark *tm)
15780 {
15781 #if HAVE_GETTIMEOFDAY
15782
15783     struct timeval timeVal;
15784     struct timezone timeZone;
15785
15786     gettimeofday(&timeVal, &timeZone);
15787     tm->sec = (long) timeVal.tv_sec;
15788     tm->ms = (int) (timeVal.tv_usec / 1000L);
15789
15790 #else /*!HAVE_GETTIMEOFDAY*/
15791 #if HAVE_FTIME
15792
15793 // include <sys/timeb.h> / moved to just above start of function
15794     struct timeb timeB;
15795
15796     ftime(&timeB);
15797     tm->sec = (long) timeB.time;
15798     tm->ms = (int) timeB.millitm;
15799
15800 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15801     tm->sec = (long) time(NULL);
15802     tm->ms = 0;
15803 #endif
15804 #endif
15805 }
15806
15807 /* Return the difference in milliseconds between two
15808    time marks.  We assume the difference will fit in a long!
15809 */
15810 long
15811 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
15812 {
15813     return 1000L*(tm2->sec - tm1->sec) +
15814            (long) (tm2->ms - tm1->ms);
15815 }
15816
15817
15818 /*
15819  * Code to manage the game clocks.
15820  *
15821  * In tournament play, black starts the clock and then white makes a move.
15822  * We give the human user a slight advantage if he is playing white---the
15823  * clocks don't run until he makes his first move, so it takes zero time.
15824  * Also, we don't account for network lag, so we could get out of sync
15825  * with GNU Chess's clock -- but then, referees are always right.
15826  */
15827
15828 static TimeMark tickStartTM;
15829 static long intendedTickLength;
15830
15831 long
15832 NextTickLength (long timeRemaining)
15833 {
15834     long nominalTickLength, nextTickLength;
15835
15836     if (timeRemaining > 0L && timeRemaining <= 10000L)
15837       nominalTickLength = 100L;
15838     else
15839       nominalTickLength = 1000L;
15840     nextTickLength = timeRemaining % nominalTickLength;
15841     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15842
15843     return nextTickLength;
15844 }
15845
15846 /* Adjust clock one minute up or down */
15847 void
15848 AdjustClock (Boolean which, int dir)
15849 {
15850     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15851     if(which) blackTimeRemaining += 60000*dir;
15852     else      whiteTimeRemaining += 60000*dir;
15853     DisplayBothClocks();
15854     adjustedClock = TRUE;
15855 }
15856
15857 /* Stop clocks and reset to a fresh time control */
15858 void
15859 ResetClocks ()
15860 {
15861     (void) StopClockTimer();
15862     if (appData.icsActive) {
15863         whiteTimeRemaining = blackTimeRemaining = 0;
15864     } else if (searchTime) {
15865         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15866         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15867     } else { /* [HGM] correct new time quote for time odds */
15868         whiteTC = blackTC = fullTimeControlString;
15869         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15870         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15871     }
15872     if (whiteFlag || blackFlag) {
15873         DisplayTitle("");
15874         whiteFlag = blackFlag = FALSE;
15875     }
15876     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15877     DisplayBothClocks();
15878     adjustedClock = FALSE;
15879 }
15880
15881 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15882
15883 /* Decrement running clock by amount of time that has passed */
15884 void
15885 DecrementClocks ()
15886 {
15887     long timeRemaining;
15888     long lastTickLength, fudge;
15889     TimeMark now;
15890
15891     if (!appData.clockMode) return;
15892     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15893
15894     GetTimeMark(&now);
15895
15896     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15897
15898     /* Fudge if we woke up a little too soon */
15899     fudge = intendedTickLength - lastTickLength;
15900     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15901
15902     if (WhiteOnMove(forwardMostMove)) {
15903         if(whiteNPS >= 0) lastTickLength = 0;
15904         timeRemaining = whiteTimeRemaining -= lastTickLength;
15905         if(timeRemaining < 0 && !appData.icsActive) {
15906             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15907             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15908                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15909                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15910             }
15911         }
15912         DisplayWhiteClock(whiteTimeRemaining - fudge,
15913                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15914     } else {
15915         if(blackNPS >= 0) lastTickLength = 0;
15916         timeRemaining = blackTimeRemaining -= lastTickLength;
15917         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15918             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15919             if(suddenDeath) {
15920                 blackStartMove = forwardMostMove;
15921                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15922             }
15923         }
15924         DisplayBlackClock(blackTimeRemaining - fudge,
15925                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15926     }
15927     if (CheckFlags()) return;
15928
15929     tickStartTM = now;
15930     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15931     StartClockTimer(intendedTickLength);
15932
15933     /* if the time remaining has fallen below the alarm threshold, sound the
15934      * alarm. if the alarm has sounded and (due to a takeback or time control
15935      * with increment) the time remaining has increased to a level above the
15936      * threshold, reset the alarm so it can sound again.
15937      */
15938
15939     if (appData.icsActive && appData.icsAlarm) {
15940
15941         /* make sure we are dealing with the user's clock */
15942         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15943                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15944            )) return;
15945
15946         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15947             alarmSounded = FALSE;
15948         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15949             PlayAlarmSound();
15950             alarmSounded = TRUE;
15951         }
15952     }
15953 }
15954
15955
15956 /* A player has just moved, so stop the previously running
15957    clock and (if in clock mode) start the other one.
15958    We redisplay both clocks in case we're in ICS mode, because
15959    ICS gives us an update to both clocks after every move.
15960    Note that this routine is called *after* forwardMostMove
15961    is updated, so the last fractional tick must be subtracted
15962    from the color that is *not* on move now.
15963 */
15964 void
15965 SwitchClocks (int newMoveNr)
15966 {
15967     long lastTickLength;
15968     TimeMark now;
15969     int flagged = FALSE;
15970
15971     GetTimeMark(&now);
15972
15973     if (StopClockTimer() && appData.clockMode) {
15974         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15975         if (!WhiteOnMove(forwardMostMove)) {
15976             if(blackNPS >= 0) lastTickLength = 0;
15977             blackTimeRemaining -= lastTickLength;
15978            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15979 //         if(pvInfoList[forwardMostMove].time == -1)
15980                  pvInfoList[forwardMostMove].time =               // use GUI time
15981                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15982         } else {
15983            if(whiteNPS >= 0) lastTickLength = 0;
15984            whiteTimeRemaining -= lastTickLength;
15985            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15986 //         if(pvInfoList[forwardMostMove].time == -1)
15987                  pvInfoList[forwardMostMove].time =
15988                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15989         }
15990         flagged = CheckFlags();
15991     }
15992     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15993     CheckTimeControl();
15994
15995     if (flagged || !appData.clockMode) return;
15996
15997     switch (gameMode) {
15998       case MachinePlaysBlack:
15999       case MachinePlaysWhite:
16000       case BeginningOfGame:
16001         if (pausing) return;
16002         break;
16003
16004       case EditGame:
16005       case PlayFromGameFile:
16006       case IcsExamining:
16007         return;
16008
16009       default:
16010         break;
16011     }
16012
16013     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16014         if(WhiteOnMove(forwardMostMove))
16015              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16016         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16017     }
16018
16019     tickStartTM = now;
16020     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16021       whiteTimeRemaining : blackTimeRemaining);
16022     StartClockTimer(intendedTickLength);
16023 }
16024
16025
16026 /* Stop both clocks */
16027 void
16028 StopClocks ()
16029 {
16030     long lastTickLength;
16031     TimeMark now;
16032
16033     if (!StopClockTimer()) return;
16034     if (!appData.clockMode) return;
16035
16036     GetTimeMark(&now);
16037
16038     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16039     if (WhiteOnMove(forwardMostMove)) {
16040         if(whiteNPS >= 0) lastTickLength = 0;
16041         whiteTimeRemaining -= lastTickLength;
16042         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16043     } else {
16044         if(blackNPS >= 0) lastTickLength = 0;
16045         blackTimeRemaining -= lastTickLength;
16046         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16047     }
16048     CheckFlags();
16049 }
16050
16051 /* Start clock of player on move.  Time may have been reset, so
16052    if clock is already running, stop and restart it. */
16053 void
16054 StartClocks ()
16055 {
16056     (void) StopClockTimer(); /* in case it was running already */
16057     DisplayBothClocks();
16058     if (CheckFlags()) return;
16059
16060     if (!appData.clockMode) return;
16061     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16062
16063     GetTimeMark(&tickStartTM);
16064     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16065       whiteTimeRemaining : blackTimeRemaining);
16066
16067    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16068     whiteNPS = blackNPS = -1;
16069     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16070        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16071         whiteNPS = first.nps;
16072     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16073        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16074         blackNPS = first.nps;
16075     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16076         whiteNPS = second.nps;
16077     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16078         blackNPS = second.nps;
16079     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16080
16081     StartClockTimer(intendedTickLength);
16082 }
16083
16084 char *
16085 TimeString (long ms)
16086 {
16087     long second, minute, hour, day;
16088     char *sign = "";
16089     static char buf[32];
16090
16091     if (ms > 0 && ms <= 9900) {
16092       /* convert milliseconds to tenths, rounding up */
16093       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16094
16095       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16096       return buf;
16097     }
16098
16099     /* convert milliseconds to seconds, rounding up */
16100     /* use floating point to avoid strangeness of integer division
16101        with negative dividends on many machines */
16102     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16103
16104     if (second < 0) {
16105         sign = "-";
16106         second = -second;
16107     }
16108
16109     day = second / (60 * 60 * 24);
16110     second = second % (60 * 60 * 24);
16111     hour = second / (60 * 60);
16112     second = second % (60 * 60);
16113     minute = second / 60;
16114     second = second % 60;
16115
16116     if (day > 0)
16117       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16118               sign, day, hour, minute, second);
16119     else if (hour > 0)
16120       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16121     else
16122       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16123
16124     return buf;
16125 }
16126
16127
16128 /*
16129  * This is necessary because some C libraries aren't ANSI C compliant yet.
16130  */
16131 char *
16132 StrStr (char *string, char *match)
16133 {
16134     int i, length;
16135
16136     length = strlen(match);
16137
16138     for (i = strlen(string) - length; i >= 0; i--, string++)
16139       if (!strncmp(match, string, length))
16140         return string;
16141
16142     return NULL;
16143 }
16144
16145 char *
16146 StrCaseStr (char *string, char *match)
16147 {
16148     int i, j, length;
16149
16150     length = strlen(match);
16151
16152     for (i = strlen(string) - length; i >= 0; i--, string++) {
16153         for (j = 0; j < length; j++) {
16154             if (ToLower(match[j]) != ToLower(string[j]))
16155               break;
16156         }
16157         if (j == length) return string;
16158     }
16159
16160     return NULL;
16161 }
16162
16163 #ifndef _amigados
16164 int
16165 StrCaseCmp (char *s1, char *s2)
16166 {
16167     char c1, c2;
16168
16169     for (;;) {
16170         c1 = ToLower(*s1++);
16171         c2 = ToLower(*s2++);
16172         if (c1 > c2) return 1;
16173         if (c1 < c2) return -1;
16174         if (c1 == NULLCHAR) return 0;
16175     }
16176 }
16177
16178
16179 int
16180 ToLower (int c)
16181 {
16182     return isupper(c) ? tolower(c) : c;
16183 }
16184
16185
16186 int
16187 ToUpper (int c)
16188 {
16189     return islower(c) ? toupper(c) : c;
16190 }
16191 #endif /* !_amigados    */
16192
16193 char *
16194 StrSave (char *s)
16195 {
16196   char *ret;
16197
16198   if ((ret = (char *) malloc(strlen(s) + 1)))
16199     {
16200       safeStrCpy(ret, s, strlen(s)+1);
16201     }
16202   return ret;
16203 }
16204
16205 char *
16206 StrSavePtr (char *s, char **savePtr)
16207 {
16208     if (*savePtr) {
16209         free(*savePtr);
16210     }
16211     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16212       safeStrCpy(*savePtr, s, strlen(s)+1);
16213     }
16214     return(*savePtr);
16215 }
16216
16217 char *
16218 PGNDate ()
16219 {
16220     time_t clock;
16221     struct tm *tm;
16222     char buf[MSG_SIZ];
16223
16224     clock = time((time_t *)NULL);
16225     tm = localtime(&clock);
16226     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16227             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16228     return StrSave(buf);
16229 }
16230
16231
16232 char *
16233 PositionToFEN (int move, char *overrideCastling)
16234 {
16235     int i, j, fromX, fromY, toX, toY;
16236     int whiteToPlay;
16237     char buf[MSG_SIZ];
16238     char *p, *q;
16239     int emptycount;
16240     ChessSquare piece;
16241
16242     whiteToPlay = (gameMode == EditPosition) ?
16243       !blackPlaysFirst : (move % 2 == 0);
16244     p = buf;
16245
16246     /* Piece placement data */
16247     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16248         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16249         emptycount = 0;
16250         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16251             if (boards[move][i][j] == EmptySquare) {
16252                 emptycount++;
16253             } else { ChessSquare piece = boards[move][i][j];
16254                 if (emptycount > 0) {
16255                     if(emptycount<10) /* [HGM] can be >= 10 */
16256                         *p++ = '0' + emptycount;
16257                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16258                     emptycount = 0;
16259                 }
16260                 if(PieceToChar(piece) == '+') {
16261                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16262                     *p++ = '+';
16263                     piece = (ChessSquare)(DEMOTED piece);
16264                 }
16265                 *p++ = PieceToChar(piece);
16266                 if(p[-1] == '~') {
16267                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16268                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16269                     *p++ = '~';
16270                 }
16271             }
16272         }
16273         if (emptycount > 0) {
16274             if(emptycount<10) /* [HGM] can be >= 10 */
16275                 *p++ = '0' + emptycount;
16276             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16277             emptycount = 0;
16278         }
16279         *p++ = '/';
16280     }
16281     *(p - 1) = ' ';
16282
16283     /* [HGM] print Crazyhouse or Shogi holdings */
16284     if( gameInfo.holdingsWidth ) {
16285         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16286         q = p;
16287         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16288             piece = boards[move][i][BOARD_WIDTH-1];
16289             if( piece != EmptySquare )
16290               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16291                   *p++ = PieceToChar(piece);
16292         }
16293         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16294             piece = boards[move][BOARD_HEIGHT-i-1][0];
16295             if( piece != EmptySquare )
16296               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16297                   *p++ = PieceToChar(piece);
16298         }
16299
16300         if( q == p ) *p++ = '-';
16301         *p++ = ']';
16302         *p++ = ' ';
16303     }
16304
16305     /* Active color */
16306     *p++ = whiteToPlay ? 'w' : 'b';
16307     *p++ = ' ';
16308
16309   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16310     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16311   } else {
16312   if(nrCastlingRights) {
16313      q = p;
16314      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16315        /* [HGM] write directly from rights */
16316            if(boards[move][CASTLING][2] != NoRights &&
16317               boards[move][CASTLING][0] != NoRights   )
16318                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16319            if(boards[move][CASTLING][2] != NoRights &&
16320               boards[move][CASTLING][1] != NoRights   )
16321                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16322            if(boards[move][CASTLING][5] != NoRights &&
16323               boards[move][CASTLING][3] != NoRights   )
16324                 *p++ = boards[move][CASTLING][3] + AAA;
16325            if(boards[move][CASTLING][5] != NoRights &&
16326               boards[move][CASTLING][4] != NoRights   )
16327                 *p++ = boards[move][CASTLING][4] + AAA;
16328      } else {
16329
16330         /* [HGM] write true castling rights */
16331         if( nrCastlingRights == 6 ) {
16332             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16333                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16334             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16335                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16336             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16337                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16338             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16339                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16340         }
16341      }
16342      if (q == p) *p++ = '-'; /* No castling rights */
16343      *p++ = ' ';
16344   }
16345
16346   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16347      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16348     /* En passant target square */
16349     if (move > backwardMostMove) {
16350         fromX = moveList[move - 1][0] - AAA;
16351         fromY = moveList[move - 1][1] - ONE;
16352         toX = moveList[move - 1][2] - AAA;
16353         toY = moveList[move - 1][3] - ONE;
16354         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16355             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16356             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16357             fromX == toX) {
16358             /* 2-square pawn move just happened */
16359             *p++ = toX + AAA;
16360             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16361         } else {
16362             *p++ = '-';
16363         }
16364     } else if(move == backwardMostMove) {
16365         // [HGM] perhaps we should always do it like this, and forget the above?
16366         if((signed char)boards[move][EP_STATUS] >= 0) {
16367             *p++ = boards[move][EP_STATUS] + AAA;
16368             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16369         } else {
16370             *p++ = '-';
16371         }
16372     } else {
16373         *p++ = '-';
16374     }
16375     *p++ = ' ';
16376   }
16377   }
16378
16379     /* [HGM] find reversible plies */
16380     {   int i = 0, j=move;
16381
16382         if (appData.debugMode) { int k;
16383             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16384             for(k=backwardMostMove; k<=forwardMostMove; k++)
16385                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16386
16387         }
16388
16389         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16390         if( j == backwardMostMove ) i += initialRulePlies;
16391         sprintf(p, "%d ", i);
16392         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16393     }
16394     /* Fullmove number */
16395     sprintf(p, "%d", (move / 2) + 1);
16396
16397     return StrSave(buf);
16398 }
16399
16400 Boolean
16401 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16402 {
16403     int i, j;
16404     char *p, c;
16405     int emptycount;
16406     ChessSquare piece;
16407
16408     p = fen;
16409
16410     /* [HGM] by default clear Crazyhouse holdings, if present */
16411     if(gameInfo.holdingsWidth) {
16412        for(i=0; i<BOARD_HEIGHT; i++) {
16413            board[i][0]             = EmptySquare; /* black holdings */
16414            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16415            board[i][1]             = (ChessSquare) 0; /* black counts */
16416            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16417        }
16418     }
16419
16420     /* Piece placement data */
16421     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16422         j = 0;
16423         for (;;) {
16424             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16425                 if (*p == '/') p++;
16426                 emptycount = gameInfo.boardWidth - j;
16427                 while (emptycount--)
16428                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16429                 break;
16430 #if(BOARD_FILES >= 10)
16431             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16432                 p++; emptycount=10;
16433                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16434                 while (emptycount--)
16435                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16436 #endif
16437             } else if (isdigit(*p)) {
16438                 emptycount = *p++ - '0';
16439                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16440                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16441                 while (emptycount--)
16442                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16443             } else if (*p == '+' || isalpha(*p)) {
16444                 if (j >= gameInfo.boardWidth) return FALSE;
16445                 if(*p=='+') {
16446                     piece = CharToPiece(*++p);
16447                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16448                     piece = (ChessSquare) (PROMOTED piece ); p++;
16449                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16450                 } else piece = CharToPiece(*p++);
16451
16452                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16453                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16454                     piece = (ChessSquare) (PROMOTED piece);
16455                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16456                     p++;
16457                 }
16458                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16459             } else {
16460                 return FALSE;
16461             }
16462         }
16463     }
16464     while (*p == '/' || *p == ' ') p++;
16465
16466     /* [HGM] look for Crazyhouse holdings here */
16467     while(*p==' ') p++;
16468     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16469         if(*p == '[') p++;
16470         if(*p == '-' ) p++; /* empty holdings */ else {
16471             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16472             /* if we would allow FEN reading to set board size, we would   */
16473             /* have to add holdings and shift the board read so far here   */
16474             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16475                 p++;
16476                 if((int) piece >= (int) BlackPawn ) {
16477                     i = (int)piece - (int)BlackPawn;
16478                     i = PieceToNumber((ChessSquare)i);
16479                     if( i >= gameInfo.holdingsSize ) return FALSE;
16480                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16481                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16482                 } else {
16483                     i = (int)piece - (int)WhitePawn;
16484                     i = PieceToNumber((ChessSquare)i);
16485                     if( i >= gameInfo.holdingsSize ) return FALSE;
16486                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16487                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16488                 }
16489             }
16490         }
16491         if(*p == ']') p++;
16492     }
16493
16494     while(*p == ' ') p++;
16495
16496     /* Active color */
16497     c = *p++;
16498     if(appData.colorNickNames) {
16499       if( c == appData.colorNickNames[0] ) c = 'w'; else
16500       if( c == appData.colorNickNames[1] ) c = 'b';
16501     }
16502     switch (c) {
16503       case 'w':
16504         *blackPlaysFirst = FALSE;
16505         break;
16506       case 'b':
16507         *blackPlaysFirst = TRUE;
16508         break;
16509       default:
16510         return FALSE;
16511     }
16512
16513     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16514     /* return the extra info in global variiables             */
16515
16516     /* set defaults in case FEN is incomplete */
16517     board[EP_STATUS] = EP_UNKNOWN;
16518     for(i=0; i<nrCastlingRights; i++ ) {
16519         board[CASTLING][i] =
16520             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16521     }   /* assume possible unless obviously impossible */
16522     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16523     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16524     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16525                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16526     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16527     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16528     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16529                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16530     FENrulePlies = 0;
16531
16532     while(*p==' ') p++;
16533     if(nrCastlingRights) {
16534       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16535           /* castling indicator present, so default becomes no castlings */
16536           for(i=0; i<nrCastlingRights; i++ ) {
16537                  board[CASTLING][i] = NoRights;
16538           }
16539       }
16540       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16541              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16542              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16543              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16544         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16545
16546         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16547             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16548             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16549         }
16550         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16551             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16552         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16553                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16554         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16555                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16556         switch(c) {
16557           case'K':
16558               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16559               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16560               board[CASTLING][2] = whiteKingFile;
16561               break;
16562           case'Q':
16563               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16564               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16565               board[CASTLING][2] = whiteKingFile;
16566               break;
16567           case'k':
16568               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16569               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16570               board[CASTLING][5] = blackKingFile;
16571               break;
16572           case'q':
16573               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16574               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16575               board[CASTLING][5] = blackKingFile;
16576           case '-':
16577               break;
16578           default: /* FRC castlings */
16579               if(c >= 'a') { /* black rights */
16580                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16581                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16582                   if(i == BOARD_RGHT) break;
16583                   board[CASTLING][5] = i;
16584                   c -= AAA;
16585                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16586                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16587                   if(c > i)
16588                       board[CASTLING][3] = c;
16589                   else
16590                       board[CASTLING][4] = c;
16591               } else { /* white rights */
16592                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16593                     if(board[0][i] == WhiteKing) break;
16594                   if(i == BOARD_RGHT) break;
16595                   board[CASTLING][2] = i;
16596                   c -= AAA - 'a' + 'A';
16597                   if(board[0][c] >= WhiteKing) break;
16598                   if(c > i)
16599                       board[CASTLING][0] = c;
16600                   else
16601                       board[CASTLING][1] = c;
16602               }
16603         }
16604       }
16605       for(i=0; i<nrCastlingRights; i++)
16606         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16607     if (appData.debugMode) {
16608         fprintf(debugFP, "FEN castling rights:");
16609         for(i=0; i<nrCastlingRights; i++)
16610         fprintf(debugFP, " %d", board[CASTLING][i]);
16611         fprintf(debugFP, "\n");
16612     }
16613
16614       while(*p==' ') p++;
16615     }
16616
16617     /* read e.p. field in games that know e.p. capture */
16618     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16619        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16620       if(*p=='-') {
16621         p++; board[EP_STATUS] = EP_NONE;
16622       } else {
16623          char c = *p++ - AAA;
16624
16625          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16626          if(*p >= '0' && *p <='9') p++;
16627          board[EP_STATUS] = c;
16628       }
16629     }
16630
16631
16632     if(sscanf(p, "%d", &i) == 1) {
16633         FENrulePlies = i; /* 50-move ply counter */
16634         /* (The move number is still ignored)    */
16635     }
16636
16637     return TRUE;
16638 }
16639
16640 void
16641 EditPositionPasteFEN (char *fen)
16642 {
16643   if (fen != NULL) {
16644     Board initial_position;
16645
16646     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16647       DisplayError(_("Bad FEN position in clipboard"), 0);
16648       return ;
16649     } else {
16650       int savedBlackPlaysFirst = blackPlaysFirst;
16651       EditPositionEvent();
16652       blackPlaysFirst = savedBlackPlaysFirst;
16653       CopyBoard(boards[0], initial_position);
16654       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16655       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16656       DisplayBothClocks();
16657       DrawPosition(FALSE, boards[currentMove]);
16658     }
16659   }
16660 }
16661
16662 static char cseq[12] = "\\   ";
16663
16664 Boolean
16665 set_cont_sequence (char *new_seq)
16666 {
16667     int len;
16668     Boolean ret;
16669
16670     // handle bad attempts to set the sequence
16671         if (!new_seq)
16672                 return 0; // acceptable error - no debug
16673
16674     len = strlen(new_seq);
16675     ret = (len > 0) && (len < sizeof(cseq));
16676     if (ret)
16677       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16678     else if (appData.debugMode)
16679       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16680     return ret;
16681 }
16682
16683 /*
16684     reformat a source message so words don't cross the width boundary.  internal
16685     newlines are not removed.  returns the wrapped size (no null character unless
16686     included in source message).  If dest is NULL, only calculate the size required
16687     for the dest buffer.  lp argument indicats line position upon entry, and it's
16688     passed back upon exit.
16689 */
16690 int
16691 wrap (char *dest, char *src, int count, int width, int *lp)
16692 {
16693     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16694
16695     cseq_len = strlen(cseq);
16696     old_line = line = *lp;
16697     ansi = len = clen = 0;
16698
16699     for (i=0; i < count; i++)
16700     {
16701         if (src[i] == '\033')
16702             ansi = 1;
16703
16704         // if we hit the width, back up
16705         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16706         {
16707             // store i & len in case the word is too long
16708             old_i = i, old_len = len;
16709
16710             // find the end of the last word
16711             while (i && src[i] != ' ' && src[i] != '\n')
16712             {
16713                 i--;
16714                 len--;
16715             }
16716
16717             // word too long?  restore i & len before splitting it
16718             if ((old_i-i+clen) >= width)
16719             {
16720                 i = old_i;
16721                 len = old_len;
16722             }
16723
16724             // extra space?
16725             if (i && src[i-1] == ' ')
16726                 len--;
16727
16728             if (src[i] != ' ' && src[i] != '\n')
16729             {
16730                 i--;
16731                 if (len)
16732                     len--;
16733             }
16734
16735             // now append the newline and continuation sequence
16736             if (dest)
16737                 dest[len] = '\n';
16738             len++;
16739             if (dest)
16740                 strncpy(dest+len, cseq, cseq_len);
16741             len += cseq_len;
16742             line = cseq_len;
16743             clen = cseq_len;
16744             continue;
16745         }
16746
16747         if (dest)
16748             dest[len] = src[i];
16749         len++;
16750         if (!ansi)
16751             line++;
16752         if (src[i] == '\n')
16753             line = 0;
16754         if (src[i] == 'm')
16755             ansi = 0;
16756     }
16757     if (dest && appData.debugMode)
16758     {
16759         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16760             count, width, line, len, *lp);
16761         show_bytes(debugFP, src, count);
16762         fprintf(debugFP, "\ndest: ");
16763         show_bytes(debugFP, dest, len);
16764         fprintf(debugFP, "\n");
16765     }
16766     *lp = dest ? line : old_line;
16767
16768     return len;
16769 }
16770
16771 // [HGM] vari: routines for shelving variations
16772 Boolean modeRestore = FALSE;
16773
16774 void
16775 PushInner (int firstMove, int lastMove)
16776 {
16777         int i, j, nrMoves = lastMove - firstMove;
16778
16779         // push current tail of game on stack
16780         savedResult[storedGames] = gameInfo.result;
16781         savedDetails[storedGames] = gameInfo.resultDetails;
16782         gameInfo.resultDetails = NULL;
16783         savedFirst[storedGames] = firstMove;
16784         savedLast [storedGames] = lastMove;
16785         savedFramePtr[storedGames] = framePtr;
16786         framePtr -= nrMoves; // reserve space for the boards
16787         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16788             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16789             for(j=0; j<MOVE_LEN; j++)
16790                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16791             for(j=0; j<2*MOVE_LEN; j++)
16792                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16793             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16794             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16795             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16796             pvInfoList[firstMove+i-1].depth = 0;
16797             commentList[framePtr+i] = commentList[firstMove+i];
16798             commentList[firstMove+i] = NULL;
16799         }
16800
16801         storedGames++;
16802         forwardMostMove = firstMove; // truncate game so we can start variation
16803 }
16804
16805 void
16806 PushTail (int firstMove, int lastMove)
16807 {
16808         if(appData.icsActive) { // only in local mode
16809                 forwardMostMove = currentMove; // mimic old ICS behavior
16810                 return;
16811         }
16812         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16813
16814         PushInner(firstMove, lastMove);
16815         if(storedGames == 1) GreyRevert(FALSE);
16816         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16817 }
16818
16819 void
16820 PopInner (Boolean annotate)
16821 {
16822         int i, j, nrMoves;
16823         char buf[8000], moveBuf[20];
16824
16825         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16826         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16827         nrMoves = savedLast[storedGames] - currentMove;
16828         if(annotate) {
16829                 int cnt = 10;
16830                 if(!WhiteOnMove(currentMove))
16831                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16832                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16833                 for(i=currentMove; i<forwardMostMove; i++) {
16834                         if(WhiteOnMove(i))
16835                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16836                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16837                         strcat(buf, moveBuf);
16838                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16839                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16840                 }
16841                 strcat(buf, ")");
16842         }
16843         for(i=1; i<=nrMoves; i++) { // copy last variation back
16844             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16845             for(j=0; j<MOVE_LEN; j++)
16846                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16847             for(j=0; j<2*MOVE_LEN; j++)
16848                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16849             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16850             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16851             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16852             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16853             commentList[currentMove+i] = commentList[framePtr+i];
16854             commentList[framePtr+i] = NULL;
16855         }
16856         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16857         framePtr = savedFramePtr[storedGames];
16858         gameInfo.result = savedResult[storedGames];
16859         if(gameInfo.resultDetails != NULL) {
16860             free(gameInfo.resultDetails);
16861       }
16862         gameInfo.resultDetails = savedDetails[storedGames];
16863         forwardMostMove = currentMove + nrMoves;
16864 }
16865
16866 Boolean
16867 PopTail (Boolean annotate)
16868 {
16869         if(appData.icsActive) return FALSE; // only in local mode
16870         if(!storedGames) return FALSE; // sanity
16871         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16872
16873         PopInner(annotate);
16874         if(currentMove < forwardMostMove) ForwardEvent(); else
16875         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16876
16877         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16878         return TRUE;
16879 }
16880
16881 void
16882 CleanupTail ()
16883 {       // remove all shelved variations
16884         int i;
16885         for(i=0; i<storedGames; i++) {
16886             if(savedDetails[i])
16887                 free(savedDetails[i]);
16888             savedDetails[i] = NULL;
16889         }
16890         for(i=framePtr; i<MAX_MOVES; i++) {
16891                 if(commentList[i]) free(commentList[i]);
16892                 commentList[i] = NULL;
16893         }
16894         framePtr = MAX_MOVES-1;
16895         storedGames = 0;
16896 }
16897
16898 void
16899 LoadVariation (int index, char *text)
16900 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16901         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16902         int level = 0, move;
16903
16904         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16905         // first find outermost bracketing variation
16906         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16907             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16908                 if(*p == '{') wait = '}'; else
16909                 if(*p == '[') wait = ']'; else
16910                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16911                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16912             }
16913             if(*p == wait) wait = NULLCHAR; // closing ]} found
16914             p++;
16915         }
16916         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16917         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16918         end[1] = NULLCHAR; // clip off comment beyond variation
16919         ToNrEvent(currentMove-1);
16920         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16921         // kludge: use ParsePV() to append variation to game
16922         move = currentMove;
16923         ParsePV(start, TRUE, TRUE);
16924         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16925         ClearPremoveHighlights();
16926         CommentPopDown();
16927         ToNrEvent(currentMove+1);
16928 }
16929