code cleanup: make function definition confirm to GNU coding style
[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                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2933                             SendToPlayer(tmp, strlen(tmp));
2934                         }
2935                         next_out = i+1; // [HGM] suppress printing in ICS window
2936                     }
2937                     started = STARTED_NONE;
2938                 } else {
2939                     /* Don't match patterns against characters in comment */
2940                     i++;
2941                     continue;
2942                 }
2943             }
2944             if (started == STARTED_CHATTER) {
2945                 if (buf[i] != '\n') {
2946                     /* Don't match patterns against characters in chatter */
2947                     i++;
2948                     continue;
2949                 }
2950                 started = STARTED_NONE;
2951                 if(suppressKibitz) next_out = i+1;
2952             }
2953
2954             /* Kludge to deal with rcmd protocol */
2955             if (firstTime && looking_at(buf, &i, "\001*")) {
2956                 DisplayFatalError(&buf[1], 0, 1);
2957                 continue;
2958             } else {
2959                 firstTime = FALSE;
2960             }
2961
2962             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2963                 ics_type = ICS_ICC;
2964                 ics_prefix = "/";
2965                 if (appData.debugMode)
2966                   fprintf(debugFP, "ics_type %d\n", ics_type);
2967                 continue;
2968             }
2969             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2970                 ics_type = ICS_FICS;
2971                 ics_prefix = "$";
2972                 if (appData.debugMode)
2973                   fprintf(debugFP, "ics_type %d\n", ics_type);
2974                 continue;
2975             }
2976             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2977                 ics_type = ICS_CHESSNET;
2978                 ics_prefix = "/";
2979                 if (appData.debugMode)
2980                   fprintf(debugFP, "ics_type %d\n", ics_type);
2981                 continue;
2982             }
2983
2984             if (!loggedOn &&
2985                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2986                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2987                  looking_at(buf, &i, "will be \"*\""))) {
2988               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2989               continue;
2990             }
2991
2992             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2993               char buf[MSG_SIZ];
2994               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2995               DisplayIcsInteractionTitle(buf);
2996               have_set_title = TRUE;
2997             }
2998
2999             /* skip finger notes */
3000             if (started == STARTED_NONE &&
3001                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3002                  (buf[i] == '1' && buf[i+1] == '0')) &&
3003                 buf[i+2] == ':' && buf[i+3] == ' ') {
3004               started = STARTED_CHATTER;
3005               i += 3;
3006               continue;
3007             }
3008
3009             oldi = i;
3010             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3011             if(appData.seekGraph) {
3012                 if(soughtPending && MatchSoughtLine(buf+i)) {
3013                     i = strstr(buf+i, "rated") - buf;
3014                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3015                     next_out = leftover_start = i;
3016                     started = STARTED_CHATTER;
3017                     suppressKibitz = TRUE;
3018                     continue;
3019                 }
3020                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3021                         && looking_at(buf, &i, "* ads displayed")) {
3022                     soughtPending = FALSE;
3023                     seekGraphUp = TRUE;
3024                     DrawSeekGraph();
3025                     continue;
3026                 }
3027                 if(appData.autoRefresh) {
3028                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3029                         int s = (ics_type == ICS_ICC); // ICC format differs
3030                         if(seekGraphUp)
3031                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3032                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3033                         looking_at(buf, &i, "*% "); // eat prompt
3034                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3035                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3036                         next_out = i; // suppress
3037                         continue;
3038                     }
3039                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3040                         char *p = star_match[0];
3041                         while(*p) {
3042                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3043                             while(*p && *p++ != ' '); // next
3044                         }
3045                         looking_at(buf, &i, "*% "); // eat prompt
3046                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3047                         next_out = i;
3048                         continue;
3049                     }
3050                 }
3051             }
3052
3053             /* skip formula vars */
3054             if (started == STARTED_NONE &&
3055                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3056               started = STARTED_CHATTER;
3057               i += 3;
3058               continue;
3059             }
3060
3061             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3062             if (appData.autoKibitz && started == STARTED_NONE &&
3063                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3064                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3065                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3066                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3067                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3068                         suppressKibitz = TRUE;
3069                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3070                         next_out = i;
3071                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3072                                 && (gameMode == IcsPlayingWhite)) ||
3073                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3074                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3075                             started = STARTED_CHATTER; // own kibitz we simply discard
3076                         else {
3077                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3078                             parse_pos = 0; parse[0] = NULLCHAR;
3079                             savingComment = TRUE;
3080                             suppressKibitz = gameMode != IcsObserving ? 2 :
3081                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3082                         }
3083                         continue;
3084                 } else
3085                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3086                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3087                          && atoi(star_match[0])) {
3088                     // suppress the acknowledgements of our own autoKibitz
3089                     char *p;
3090                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3091                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3092                     SendToPlayer(star_match[0], strlen(star_match[0]));
3093                     if(looking_at(buf, &i, "*% ")) // eat prompt
3094                         suppressKibitz = FALSE;
3095                     next_out = i;
3096                     continue;
3097                 }
3098             } // [HGM] kibitz: end of patch
3099
3100             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3101
3102             // [HGM] chat: intercept tells by users for which we have an open chat window
3103             channel = -1;
3104             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3105                                            looking_at(buf, &i, "* whispers:") ||
3106                                            looking_at(buf, &i, "* kibitzes:") ||
3107                                            looking_at(buf, &i, "* shouts:") ||
3108                                            looking_at(buf, &i, "* c-shouts:") ||
3109                                            looking_at(buf, &i, "--> * ") ||
3110                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3111                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3112                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3113                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3114                 int p;
3115                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3116                 chattingPartner = -1;
3117
3118                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3119                 for(p=0; p<MAX_CHAT; p++) {
3120                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3121                     talker[0] = '['; strcat(talker, "] ");
3122                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3123                     chattingPartner = p; break;
3124                     }
3125                 } else
3126                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3127                 for(p=0; p<MAX_CHAT; p++) {
3128                     if(!strcmp("kibitzes", chatPartner[p])) {
3129                         talker[0] = '['; strcat(talker, "] ");
3130                         chattingPartner = p; break;
3131                     }
3132                 } else
3133                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3134                 for(p=0; p<MAX_CHAT; p++) {
3135                     if(!strcmp("whispers", chatPartner[p])) {
3136                         talker[0] = '['; strcat(talker, "] ");
3137                         chattingPartner = p; break;
3138                     }
3139                 } else
3140                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3141                   if(buf[i-8] == '-' && buf[i-3] == 't')
3142                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3143                     if(!strcmp("c-shouts", chatPartner[p])) {
3144                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3145                         chattingPartner = p; break;
3146                     }
3147                   }
3148                   if(chattingPartner < 0)
3149                   for(p=0; p<MAX_CHAT; p++) {
3150                     if(!strcmp("shouts", chatPartner[p])) {
3151                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3152                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3153                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3154                         chattingPartner = p; break;
3155                     }
3156                   }
3157                 }
3158                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3159                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3160                     talker[0] = 0; Colorize(ColorTell, FALSE);
3161                     chattingPartner = p; break;
3162                 }
3163                 if(chattingPartner<0) i = oldi; else {
3164                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3165                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3166                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3167                     started = STARTED_COMMENT;
3168                     parse_pos = 0; parse[0] = NULLCHAR;
3169                     savingComment = 3 + chattingPartner; // counts as TRUE
3170                     suppressKibitz = TRUE;
3171                     continue;
3172                 }
3173             } // [HGM] chat: end of patch
3174
3175           backup = i;
3176             if (appData.zippyTalk || appData.zippyPlay) {
3177                 /* [DM] Backup address for color zippy lines */
3178 #if ZIPPY
3179                if (loggedOn == TRUE)
3180                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3181                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3182 #endif
3183             } // [DM] 'else { ' deleted
3184                 if (
3185                     /* Regular tells and says */
3186                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3187                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3188                     looking_at(buf, &i, "* says: ") ||
3189                     /* Don't color "message" or "messages" output */
3190                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3191                     looking_at(buf, &i, "*. * at *:*: ") ||
3192                     looking_at(buf, &i, "--* (*:*): ") ||
3193                     /* Message notifications (same color as tells) */
3194                     looking_at(buf, &i, "* has left a message ") ||
3195                     looking_at(buf, &i, "* just sent you a message:\n") ||
3196                     /* Whispers and kibitzes */
3197                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3198                     looking_at(buf, &i, "* kibitzes: ") ||
3199                     /* Channel tells */
3200                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3201
3202                   if (tkind == 1 && strchr(star_match[0], ':')) {
3203                       /* Avoid "tells you:" spoofs in channels */
3204                      tkind = 3;
3205                   }
3206                   if (star_match[0][0] == NULLCHAR ||
3207                       strchr(star_match[0], ' ') ||
3208                       (tkind == 3 && strchr(star_match[1], ' '))) {
3209                     /* Reject bogus matches */
3210                     i = oldi;
3211                   } else {
3212                     if (appData.colorize) {
3213                       if (oldi > next_out) {
3214                         SendToPlayer(&buf[next_out], oldi - next_out);
3215                         next_out = oldi;
3216                       }
3217                       switch (tkind) {
3218                       case 1:
3219                         Colorize(ColorTell, FALSE);
3220                         curColor = ColorTell;
3221                         break;
3222                       case 2:
3223                         Colorize(ColorKibitz, FALSE);
3224                         curColor = ColorKibitz;
3225                         break;
3226                       case 3:
3227                         p = strrchr(star_match[1], '(');
3228                         if (p == NULL) {
3229                           p = star_match[1];
3230                         } else {
3231                           p++;
3232                         }
3233                         if (atoi(p) == 1) {
3234                           Colorize(ColorChannel1, FALSE);
3235                           curColor = ColorChannel1;
3236                         } else {
3237                           Colorize(ColorChannel, FALSE);
3238                           curColor = ColorChannel;
3239                         }
3240                         break;
3241                       case 5:
3242                         curColor = ColorNormal;
3243                         break;
3244                       }
3245                     }
3246                     if (started == STARTED_NONE && appData.autoComment &&
3247                         (gameMode == IcsObserving ||
3248                          gameMode == IcsPlayingWhite ||
3249                          gameMode == IcsPlayingBlack)) {
3250                       parse_pos = i - oldi;
3251                       memcpy(parse, &buf[oldi], parse_pos);
3252                       parse[parse_pos] = NULLCHAR;
3253                       started = STARTED_COMMENT;
3254                       savingComment = TRUE;
3255                     } else {
3256                       started = STARTED_CHATTER;
3257                       savingComment = FALSE;
3258                     }
3259                     loggedOn = TRUE;
3260                     continue;
3261                   }
3262                 }
3263
3264                 if (looking_at(buf, &i, "* s-shouts: ") ||
3265                     looking_at(buf, &i, "* c-shouts: ")) {
3266                     if (appData.colorize) {
3267                         if (oldi > next_out) {
3268                             SendToPlayer(&buf[next_out], oldi - next_out);
3269                             next_out = oldi;
3270                         }
3271                         Colorize(ColorSShout, FALSE);
3272                         curColor = ColorSShout;
3273                     }
3274                     loggedOn = TRUE;
3275                     started = STARTED_CHATTER;
3276                     continue;
3277                 }
3278
3279                 if (looking_at(buf, &i, "--->")) {
3280                     loggedOn = TRUE;
3281                     continue;
3282                 }
3283
3284                 if (looking_at(buf, &i, "* shouts: ") ||
3285                     looking_at(buf, &i, "--> ")) {
3286                     if (appData.colorize) {
3287                         if (oldi > next_out) {
3288                             SendToPlayer(&buf[next_out], oldi - next_out);
3289                             next_out = oldi;
3290                         }
3291                         Colorize(ColorShout, FALSE);
3292                         curColor = ColorShout;
3293                     }
3294                     loggedOn = TRUE;
3295                     started = STARTED_CHATTER;
3296                     continue;
3297                 }
3298
3299                 if (looking_at( buf, &i, "Challenge:")) {
3300                     if (appData.colorize) {
3301                         if (oldi > next_out) {
3302                             SendToPlayer(&buf[next_out], oldi - next_out);
3303                             next_out = oldi;
3304                         }
3305                         Colorize(ColorChallenge, FALSE);
3306                         curColor = ColorChallenge;
3307                     }
3308                     loggedOn = TRUE;
3309                     continue;
3310                 }
3311
3312                 if (looking_at(buf, &i, "* offers you") ||
3313                     looking_at(buf, &i, "* offers to be") ||
3314                     looking_at(buf, &i, "* would like to") ||
3315                     looking_at(buf, &i, "* requests to") ||
3316                     looking_at(buf, &i, "Your opponent offers") ||
3317                     looking_at(buf, &i, "Your opponent requests")) {
3318
3319                     if (appData.colorize) {
3320                         if (oldi > next_out) {
3321                             SendToPlayer(&buf[next_out], oldi - next_out);
3322                             next_out = oldi;
3323                         }
3324                         Colorize(ColorRequest, FALSE);
3325                         curColor = ColorRequest;
3326                     }
3327                     continue;
3328                 }
3329
3330                 if (looking_at(buf, &i, "* (*) seeking")) {
3331                     if (appData.colorize) {
3332                         if (oldi > next_out) {
3333                             SendToPlayer(&buf[next_out], oldi - next_out);
3334                             next_out = oldi;
3335                         }
3336                         Colorize(ColorSeek, FALSE);
3337                         curColor = ColorSeek;
3338                     }
3339                     continue;
3340             }
3341
3342           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3343
3344             if (looking_at(buf, &i, "\\   ")) {
3345                 if (prevColor != ColorNormal) {
3346                     if (oldi > next_out) {
3347                         SendToPlayer(&buf[next_out], oldi - next_out);
3348                         next_out = oldi;
3349                     }
3350                     Colorize(prevColor, TRUE);
3351                     curColor = prevColor;
3352                 }
3353                 if (savingComment) {
3354                     parse_pos = i - oldi;
3355                     memcpy(parse, &buf[oldi], parse_pos);
3356                     parse[parse_pos] = NULLCHAR;
3357                     started = STARTED_COMMENT;
3358                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3359                         chattingPartner = savingComment - 3; // kludge to remember the box
3360                 } else {
3361                     started = STARTED_CHATTER;
3362                 }
3363                 continue;
3364             }
3365
3366             if (looking_at(buf, &i, "Black Strength :") ||
3367                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3368                 looking_at(buf, &i, "<10>") ||
3369                 looking_at(buf, &i, "#@#")) {
3370                 /* Wrong board style */
3371                 loggedOn = TRUE;
3372                 SendToICS(ics_prefix);
3373                 SendToICS("set style 12\n");
3374                 SendToICS(ics_prefix);
3375                 SendToICS("refresh\n");
3376                 continue;
3377             }
3378
3379             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3380                 ICSInitScript();
3381                 have_sent_ICS_logon = 1;
3382                 continue;
3383             }
3384
3385             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3386                 (looking_at(buf, &i, "\n<12> ") ||
3387                  looking_at(buf, &i, "<12> "))) {
3388                 loggedOn = TRUE;
3389                 if (oldi > next_out) {
3390                     SendToPlayer(&buf[next_out], oldi - next_out);
3391                 }
3392                 next_out = i;
3393                 started = STARTED_BOARD;
3394                 parse_pos = 0;
3395                 continue;
3396             }
3397
3398             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3399                 looking_at(buf, &i, "<b1> ")) {
3400                 if (oldi > next_out) {
3401                     SendToPlayer(&buf[next_out], oldi - next_out);
3402                 }
3403                 next_out = i;
3404                 started = STARTED_HOLDINGS;
3405                 parse_pos = 0;
3406                 continue;
3407             }
3408
3409             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3410                 loggedOn = TRUE;
3411                 /* Header for a move list -- first line */
3412
3413                 switch (ics_getting_history) {
3414                   case H_FALSE:
3415                     switch (gameMode) {
3416                       case IcsIdle:
3417                       case BeginningOfGame:
3418                         /* User typed "moves" or "oldmoves" while we
3419                            were idle.  Pretend we asked for these
3420                            moves and soak them up so user can step
3421                            through them and/or save them.
3422                            */
3423                         Reset(FALSE, TRUE);
3424                         gameMode = IcsObserving;
3425                         ModeHighlight();
3426                         ics_gamenum = -1;
3427                         ics_getting_history = H_GOT_UNREQ_HEADER;
3428                         break;
3429                       case EditGame: /*?*/
3430                       case EditPosition: /*?*/
3431                         /* Should above feature work in these modes too? */
3432                         /* For now it doesn't */
3433                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3434                         break;
3435                       default:
3436                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3437                         break;
3438                     }
3439                     break;
3440                   case H_REQUESTED:
3441                     /* Is this the right one? */
3442                     if (gameInfo.white && gameInfo.black &&
3443                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3444                         strcmp(gameInfo.black, star_match[2]) == 0) {
3445                         /* All is well */
3446                         ics_getting_history = H_GOT_REQ_HEADER;
3447                     }
3448                     break;
3449                   case H_GOT_REQ_HEADER:
3450                   case H_GOT_UNREQ_HEADER:
3451                   case H_GOT_UNWANTED_HEADER:
3452                   case H_GETTING_MOVES:
3453                     /* Should not happen */
3454                     DisplayError(_("Error gathering move list: two headers"), 0);
3455                     ics_getting_history = H_FALSE;
3456                     break;
3457                 }
3458
3459                 /* Save player ratings into gameInfo if needed */
3460                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3461                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3462                     (gameInfo.whiteRating == -1 ||
3463                      gameInfo.blackRating == -1)) {
3464
3465                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3466                     gameInfo.blackRating = string_to_rating(star_match[3]);
3467                     if (appData.debugMode)
3468                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3469                               gameInfo.whiteRating, gameInfo.blackRating);
3470                 }
3471                 continue;
3472             }
3473
3474             if (looking_at(buf, &i,
3475               "* * match, initial time: * minute*, increment: * second")) {
3476                 /* Header for a move list -- second line */
3477                 /* Initial board will follow if this is a wild game */
3478                 if (gameInfo.event != NULL) free(gameInfo.event);
3479                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3480                 gameInfo.event = StrSave(str);
3481                 /* [HGM] we switched variant. Translate boards if needed. */
3482                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3483                 continue;
3484             }
3485
3486             if (looking_at(buf, &i, "Move  ")) {
3487                 /* Beginning of a move list */
3488                 switch (ics_getting_history) {
3489                   case H_FALSE:
3490                     /* Normally should not happen */
3491                     /* Maybe user hit reset while we were parsing */
3492                     break;
3493                   case H_REQUESTED:
3494                     /* Happens if we are ignoring a move list that is not
3495                      * the one we just requested.  Common if the user
3496                      * tries to observe two games without turning off
3497                      * getMoveList */
3498                     break;
3499                   case H_GETTING_MOVES:
3500                     /* Should not happen */
3501                     DisplayError(_("Error gathering move list: nested"), 0);
3502                     ics_getting_history = H_FALSE;
3503                     break;
3504                   case H_GOT_REQ_HEADER:
3505                     ics_getting_history = H_GETTING_MOVES;
3506                     started = STARTED_MOVES;
3507                     parse_pos = 0;
3508                     if (oldi > next_out) {
3509                         SendToPlayer(&buf[next_out], oldi - next_out);
3510                     }
3511                     break;
3512                   case H_GOT_UNREQ_HEADER:
3513                     ics_getting_history = H_GETTING_MOVES;
3514                     started = STARTED_MOVES_NOHIDE;
3515                     parse_pos = 0;
3516                     break;
3517                   case H_GOT_UNWANTED_HEADER:
3518                     ics_getting_history = H_FALSE;
3519                     break;
3520                 }
3521                 continue;
3522             }
3523
3524             if (looking_at(buf, &i, "% ") ||
3525                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3526                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3527                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3528                     soughtPending = FALSE;
3529                     seekGraphUp = TRUE;
3530                     DrawSeekGraph();
3531                 }
3532                 if(suppressKibitz) next_out = i;
3533                 savingComment = FALSE;
3534                 suppressKibitz = 0;
3535                 switch (started) {
3536                   case STARTED_MOVES:
3537                   case STARTED_MOVES_NOHIDE:
3538                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3539                     parse[parse_pos + i - oldi] = NULLCHAR;
3540                     ParseGameHistory(parse);
3541 #if ZIPPY
3542                     if (appData.zippyPlay && first.initDone) {
3543                         FeedMovesToProgram(&first, forwardMostMove);
3544                         if (gameMode == IcsPlayingWhite) {
3545                             if (WhiteOnMove(forwardMostMove)) {
3546                                 if (first.sendTime) {
3547                                   if (first.useColors) {
3548                                     SendToProgram("black\n", &first);
3549                                   }
3550                                   SendTimeRemaining(&first, TRUE);
3551                                 }
3552                                 if (first.useColors) {
3553                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3554                                 }
3555                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3556                                 first.maybeThinking = TRUE;
3557                             } else {
3558                                 if (first.usePlayother) {
3559                                   if (first.sendTime) {
3560                                     SendTimeRemaining(&first, TRUE);
3561                                   }
3562                                   SendToProgram("playother\n", &first);
3563                                   firstMove = FALSE;
3564                                 } else {
3565                                   firstMove = TRUE;
3566                                 }
3567                             }
3568                         } else if (gameMode == IcsPlayingBlack) {
3569                             if (!WhiteOnMove(forwardMostMove)) {
3570                                 if (first.sendTime) {
3571                                   if (first.useColors) {
3572                                     SendToProgram("white\n", &first);
3573                                   }
3574                                   SendTimeRemaining(&first, FALSE);
3575                                 }
3576                                 if (first.useColors) {
3577                                   SendToProgram("black\n", &first);
3578                                 }
3579                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3580                                 first.maybeThinking = TRUE;
3581                             } else {
3582                                 if (first.usePlayother) {
3583                                   if (first.sendTime) {
3584                                     SendTimeRemaining(&first, FALSE);
3585                                   }
3586                                   SendToProgram("playother\n", &first);
3587                                   firstMove = FALSE;
3588                                 } else {
3589                                   firstMove = TRUE;
3590                                 }
3591                             }
3592                         }
3593                     }
3594 #endif
3595                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3596                         /* Moves came from oldmoves or moves command
3597                            while we weren't doing anything else.
3598                            */
3599                         currentMove = forwardMostMove;
3600                         ClearHighlights();/*!!could figure this out*/
3601                         flipView = appData.flipView;
3602                         DrawPosition(TRUE, boards[currentMove]);
3603                         DisplayBothClocks();
3604                         snprintf(str, MSG_SIZ, _("%s vs. %s"),
3605                                 gameInfo.white, gameInfo.black);
3606                         DisplayTitle(str);
3607                         gameMode = IcsIdle;
3608                     } else {
3609                         /* Moves were history of an active game */
3610                         if (gameInfo.resultDetails != NULL) {
3611                             free(gameInfo.resultDetails);
3612                             gameInfo.resultDetails = NULL;
3613                         }
3614                     }
3615                     HistorySet(parseList, backwardMostMove,
3616                                forwardMostMove, currentMove-1);
3617                     DisplayMove(currentMove - 1);
3618                     if (started == STARTED_MOVES) next_out = i;
3619                     started = STARTED_NONE;
3620                     ics_getting_history = H_FALSE;
3621                     break;
3622
3623                   case STARTED_OBSERVE:
3624                     started = STARTED_NONE;
3625                     SendToICS(ics_prefix);
3626                     SendToICS("refresh\n");
3627                     break;
3628
3629                   default:
3630                     break;
3631                 }
3632                 if(bookHit) { // [HGM] book: simulate book reply
3633                     static char bookMove[MSG_SIZ]; // a bit generous?
3634
3635                     programStats.nodes = programStats.depth = programStats.time =
3636                     programStats.score = programStats.got_only_move = 0;
3637                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3638
3639                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3640                     strcat(bookMove, bookHit);
3641                     HandleMachineMove(bookMove, &first);
3642                 }
3643                 continue;
3644             }
3645
3646             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3647                  started == STARTED_HOLDINGS ||
3648                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3649                 /* Accumulate characters in move list or board */
3650                 parse[parse_pos++] = buf[i];
3651             }
3652
3653             /* Start of game messages.  Mostly we detect start of game
3654                when the first board image arrives.  On some versions
3655                of the ICS, though, we need to do a "refresh" after starting
3656                to observe in order to get the current board right away. */
3657             if (looking_at(buf, &i, "Adding game * to observation list")) {
3658                 started = STARTED_OBSERVE;
3659                 continue;
3660             }
3661
3662             /* Handle auto-observe */
3663             if (appData.autoObserve &&
3664                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3665                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3666                 char *player;
3667                 /* Choose the player that was highlighted, if any. */
3668                 if (star_match[0][0] == '\033' ||
3669                     star_match[1][0] != '\033') {
3670                     player = star_match[0];
3671                 } else {
3672                     player = star_match[2];
3673                 }
3674                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3675                         ics_prefix, StripHighlightAndTitle(player));
3676                 SendToICS(str);
3677
3678                 /* Save ratings from notify string */
3679                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3680                 player1Rating = string_to_rating(star_match[1]);
3681                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3682                 player2Rating = string_to_rating(star_match[3]);
3683
3684                 if (appData.debugMode)
3685                   fprintf(debugFP,
3686                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3687                           player1Name, player1Rating,
3688                           player2Name, player2Rating);
3689
3690                 continue;
3691             }
3692
3693             /* Deal with automatic examine mode after a game,
3694                and with IcsObserving -> IcsExamining transition */
3695             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3696                 looking_at(buf, &i, "has made you an examiner of game *")) {
3697
3698                 int gamenum = atoi(star_match[0]);
3699                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3700                     gamenum == ics_gamenum) {
3701                     /* We were already playing or observing this game;
3702                        no need to refetch history */
3703                     gameMode = IcsExamining;
3704                     if (pausing) {
3705                         pauseExamForwardMostMove = forwardMostMove;
3706                     } else if (currentMove < forwardMostMove) {
3707                         ForwardInner(forwardMostMove);
3708                     }
3709                 } else {
3710                     /* I don't think this case really can happen */
3711                     SendToICS(ics_prefix);
3712                     SendToICS("refresh\n");
3713                 }
3714                 continue;
3715             }
3716
3717             /* Error messages */
3718 //          if (ics_user_moved) {
3719             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3720                 if (looking_at(buf, &i, "Illegal move") ||
3721                     looking_at(buf, &i, "Not a legal move") ||
3722                     looking_at(buf, &i, "Your king is in check") ||
3723                     looking_at(buf, &i, "It isn't your turn") ||
3724                     looking_at(buf, &i, "It is not your move")) {
3725                     /* Illegal move */
3726                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3727                         currentMove = forwardMostMove-1;
3728                         DisplayMove(currentMove - 1); /* before DMError */
3729                         DrawPosition(FALSE, boards[currentMove]);
3730                         SwitchClocks(forwardMostMove-1); // [HGM] race
3731                         DisplayBothClocks();
3732                     }
3733                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3734                     ics_user_moved = 0;
3735                     continue;
3736                 }
3737             }
3738
3739             if (looking_at(buf, &i, "still have time") ||
3740                 looking_at(buf, &i, "not out of time") ||
3741                 looking_at(buf, &i, "either player is out of time") ||
3742                 looking_at(buf, &i, "has timeseal; checking")) {
3743                 /* We must have called his flag a little too soon */
3744                 whiteFlag = blackFlag = FALSE;
3745                 continue;
3746             }
3747
3748             if (looking_at(buf, &i, "added * seconds to") ||
3749                 looking_at(buf, &i, "seconds were added to")) {
3750                 /* Update the clocks */
3751                 SendToICS(ics_prefix);
3752                 SendToICS("refresh\n");
3753                 continue;
3754             }
3755
3756             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3757                 ics_clock_paused = TRUE;
3758                 StopClocks();
3759                 continue;
3760             }
3761
3762             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3763                 ics_clock_paused = FALSE;
3764                 StartClocks();
3765                 continue;
3766             }
3767
3768             /* Grab player ratings from the Creating: message.
3769                Note we have to check for the special case when
3770                the ICS inserts things like [white] or [black]. */
3771             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3772                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3773                 /* star_matches:
3774                    0    player 1 name (not necessarily white)
3775                    1    player 1 rating
3776                    2    empty, white, or black (IGNORED)
3777                    3    player 2 name (not necessarily black)
3778                    4    player 2 rating
3779
3780                    The names/ratings are sorted out when the game
3781                    actually starts (below).
3782                 */
3783                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3784                 player1Rating = string_to_rating(star_match[1]);
3785                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3786                 player2Rating = string_to_rating(star_match[4]);
3787
3788                 if (appData.debugMode)
3789                   fprintf(debugFP,
3790                           "Ratings from 'Creating:' %s %d, %s %d\n",
3791                           player1Name, player1Rating,
3792                           player2Name, player2Rating);
3793
3794                 continue;
3795             }
3796
3797             /* Improved generic start/end-of-game messages */
3798             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3799                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3800                 /* If tkind == 0: */
3801                 /* star_match[0] is the game number */
3802                 /*           [1] is the white player's name */
3803                 /*           [2] is the black player's name */
3804                 /* For end-of-game: */
3805                 /*           [3] is the reason for the game end */
3806                 /*           [4] is a PGN end game-token, preceded by " " */
3807                 /* For start-of-game: */
3808                 /*           [3] begins with "Creating" or "Continuing" */
3809                 /*           [4] is " *" or empty (don't care). */
3810                 int gamenum = atoi(star_match[0]);
3811                 char *whitename, *blackname, *why, *endtoken;
3812                 ChessMove endtype = EndOfFile;
3813
3814                 if (tkind == 0) {
3815                   whitename = star_match[1];
3816                   blackname = star_match[2];
3817                   why = star_match[3];
3818                   endtoken = star_match[4];
3819                 } else {
3820                   whitename = star_match[1];
3821                   blackname = star_match[3];
3822                   why = star_match[5];
3823                   endtoken = star_match[6];
3824                 }
3825
3826                 /* Game start messages */
3827                 if (strncmp(why, "Creating ", 9) == 0 ||
3828                     strncmp(why, "Continuing ", 11) == 0) {
3829                     gs_gamenum = gamenum;
3830                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3831                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3832                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3833 #if ZIPPY
3834                     if (appData.zippyPlay) {
3835                         ZippyGameStart(whitename, blackname);
3836                     }
3837 #endif /*ZIPPY*/
3838                     partnerBoardValid = FALSE; // [HGM] bughouse
3839                     continue;
3840                 }
3841
3842                 /* Game end messages */
3843                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3844                     ics_gamenum != gamenum) {
3845                     continue;
3846                 }
3847                 while (endtoken[0] == ' ') endtoken++;
3848                 switch (endtoken[0]) {
3849                   case '*':
3850                   default:
3851                     endtype = GameUnfinished;
3852                     break;
3853                   case '0':
3854                     endtype = BlackWins;
3855                     break;
3856                   case '1':
3857                     if (endtoken[1] == '/')
3858                       endtype = GameIsDrawn;
3859                     else
3860                       endtype = WhiteWins;
3861                     break;
3862                 }
3863                 GameEnds(endtype, why, GE_ICS);
3864 #if ZIPPY
3865                 if (appData.zippyPlay && first.initDone) {
3866                     ZippyGameEnd(endtype, why);
3867                     if (first.pr == NoProc) {
3868                       /* Start the next process early so that we'll
3869                          be ready for the next challenge */
3870                       StartChessProgram(&first);
3871                     }
3872                     /* Send "new" early, in case this command takes
3873                        a long time to finish, so that we'll be ready
3874                        for the next challenge. */
3875                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3876                     Reset(TRUE, TRUE);
3877                 }
3878 #endif /*ZIPPY*/
3879                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3880                 continue;
3881             }
3882
3883             if (looking_at(buf, &i, "Removing game * from observation") ||
3884                 looking_at(buf, &i, "no longer observing game *") ||
3885                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3886                 if (gameMode == IcsObserving &&
3887                     atoi(star_match[0]) == ics_gamenum)
3888                   {
3889                       /* icsEngineAnalyze */
3890                       if (appData.icsEngineAnalyze) {
3891                             ExitAnalyzeMode();
3892                             ModeHighlight();
3893                       }
3894                       StopClocks();
3895                       gameMode = IcsIdle;
3896                       ics_gamenum = -1;
3897                       ics_user_moved = FALSE;
3898                   }
3899                 continue;
3900             }
3901
3902             if (looking_at(buf, &i, "no longer examining game *")) {
3903                 if (gameMode == IcsExamining &&
3904                     atoi(star_match[0]) == ics_gamenum)
3905                   {
3906                       gameMode = IcsIdle;
3907                       ics_gamenum = -1;
3908                       ics_user_moved = FALSE;
3909                   }
3910                 continue;
3911             }
3912
3913             /* Advance leftover_start past any newlines we find,
3914                so only partial lines can get reparsed */
3915             if (looking_at(buf, &i, "\n")) {
3916                 prevColor = curColor;
3917                 if (curColor != ColorNormal) {
3918                     if (oldi > next_out) {
3919                         SendToPlayer(&buf[next_out], oldi - next_out);
3920                         next_out = oldi;
3921                     }
3922                     Colorize(ColorNormal, FALSE);
3923                     curColor = ColorNormal;
3924                 }
3925                 if (started == STARTED_BOARD) {
3926                     started = STARTED_NONE;
3927                     parse[parse_pos] = NULLCHAR;
3928                     ParseBoard12(parse);
3929                     ics_user_moved = 0;
3930
3931                     /* Send premove here */
3932                     if (appData.premove) {
3933                       char str[MSG_SIZ];
3934                       if (currentMove == 0 &&
3935                           gameMode == IcsPlayingWhite &&
3936                           appData.premoveWhite) {
3937                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3938                         if (appData.debugMode)
3939                           fprintf(debugFP, "Sending premove:\n");
3940                         SendToICS(str);
3941                       } else if (currentMove == 1 &&
3942                                  gameMode == IcsPlayingBlack &&
3943                                  appData.premoveBlack) {
3944                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3945                         if (appData.debugMode)
3946                           fprintf(debugFP, "Sending premove:\n");
3947                         SendToICS(str);
3948                       } else if (gotPremove) {
3949                         gotPremove = 0;
3950                         ClearPremoveHighlights();
3951                         if (appData.debugMode)
3952                           fprintf(debugFP, "Sending premove:\n");
3953                           UserMoveEvent(premoveFromX, premoveFromY,
3954                                         premoveToX, premoveToY,
3955                                         premovePromoChar);
3956                       }
3957                     }
3958
3959                     /* Usually suppress following prompt */
3960                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3961                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3962                         if (looking_at(buf, &i, "*% ")) {
3963                             savingComment = FALSE;
3964                             suppressKibitz = 0;
3965                         }
3966                     }
3967                     next_out = i;
3968                 } else if (started == STARTED_HOLDINGS) {
3969                     int gamenum;
3970                     char new_piece[MSG_SIZ];
3971                     started = STARTED_NONE;
3972                     parse[parse_pos] = NULLCHAR;
3973                     if (appData.debugMode)
3974                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3975                                                         parse, currentMove);
3976                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3977                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3978                         if (gameInfo.variant == VariantNormal) {
3979                           /* [HGM] We seem to switch variant during a game!
3980                            * Presumably no holdings were displayed, so we have
3981                            * to move the position two files to the right to
3982                            * create room for them!
3983                            */
3984                           VariantClass newVariant;
3985                           switch(gameInfo.boardWidth) { // base guess on board width
3986                                 case 9:  newVariant = VariantShogi; break;
3987                                 case 10: newVariant = VariantGreat; break;
3988                                 default: newVariant = VariantCrazyhouse; break;
3989                           }
3990                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3991                           /* Get a move list just to see the header, which
3992                              will tell us whether this is really bug or zh */
3993                           if (ics_getting_history == H_FALSE) {
3994                             ics_getting_history = H_REQUESTED;
3995                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3996                             SendToICS(str);
3997                           }
3998                         }
3999                         new_piece[0] = NULLCHAR;
4000                         sscanf(parse, "game %d white [%s black [%s <- %s",
4001                                &gamenum, white_holding, black_holding,
4002                                new_piece);
4003                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4004                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4005                         /* [HGM] copy holdings to board holdings area */
4006                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4007                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4008                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4009 #if ZIPPY
4010                         if (appData.zippyPlay && first.initDone) {
4011                             ZippyHoldings(white_holding, black_holding,
4012                                           new_piece);
4013                         }
4014 #endif /*ZIPPY*/
4015                         if (tinyLayout || smallLayout) {
4016                             char wh[16], bh[16];
4017                             PackHolding(wh, white_holding);
4018                             PackHolding(bh, black_holding);
4019                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4020                                     gameInfo.white, gameInfo.black);
4021                         } else {
4022                           snprintf(str, MSG_SIZ, _("%s [%s] vs. %s [%s]"),
4023                                     gameInfo.white, white_holding,
4024                                     gameInfo.black, black_holding);
4025                         }
4026                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4027                         DrawPosition(FALSE, boards[currentMove]);
4028                         DisplayTitle(str);
4029                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4030                         sscanf(parse, "game %d white [%s black [%s <- %s",
4031                                &gamenum, white_holding, black_holding,
4032                                new_piece);
4033                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4034                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4035                         /* [HGM] copy holdings to partner-board holdings area */
4036                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4037                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4038                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4039                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4040                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4041                       }
4042                     }
4043                     /* Suppress following prompt */
4044                     if (looking_at(buf, &i, "*% ")) {
4045                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4046                         savingComment = FALSE;
4047                         suppressKibitz = 0;
4048                     }
4049                     next_out = i;
4050                 }
4051                 continue;
4052             }
4053
4054             i++;                /* skip unparsed character and loop back */
4055         }
4056
4057         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4058 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4059 //          SendToPlayer(&buf[next_out], i - next_out);
4060             started != STARTED_HOLDINGS && leftover_start > next_out) {
4061             SendToPlayer(&buf[next_out], leftover_start - next_out);
4062             next_out = i;
4063         }
4064
4065         leftover_len = buf_len - leftover_start;
4066         /* if buffer ends with something we couldn't parse,
4067            reparse it after appending the next read */
4068
4069     } else if (count == 0) {
4070         RemoveInputSource(isr);
4071         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4072     } else {
4073         DisplayFatalError(_("Error reading from ICS"), error, 1);
4074     }
4075 }
4076
4077
4078 /* Board style 12 looks like this:
4079
4080    <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
4081
4082  * The "<12> " is stripped before it gets to this routine.  The two
4083  * trailing 0's (flip state and clock ticking) are later addition, and
4084  * some chess servers may not have them, or may have only the first.
4085  * Additional trailing fields may be added in the future.
4086  */
4087
4088 #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"
4089
4090 #define RELATION_OBSERVING_PLAYED    0
4091 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4092 #define RELATION_PLAYING_MYMOVE      1
4093 #define RELATION_PLAYING_NOTMYMOVE  -1
4094 #define RELATION_EXAMINING           2
4095 #define RELATION_ISOLATED_BOARD     -3
4096 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4097
4098 void
4099 ParseBoard12 (char *string)
4100 {
4101     GameMode newGameMode;
4102     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4103     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4104     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4105     char to_play, board_chars[200];
4106     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4107     char black[32], white[32];
4108     Board board;
4109     int prevMove = currentMove;
4110     int ticking = 2;
4111     ChessMove moveType;
4112     int fromX, fromY, toX, toY;
4113     char promoChar;
4114     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4115     char *bookHit = NULL; // [HGM] book
4116     Boolean weird = FALSE, reqFlag = FALSE;
4117
4118     fromX = fromY = toX = toY = -1;
4119
4120     newGame = FALSE;
4121
4122     if (appData.debugMode)
4123       fprintf(debugFP, _("Parsing board: %s\n"), string);
4124
4125     move_str[0] = NULLCHAR;
4126     elapsed_time[0] = NULLCHAR;
4127     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4128         int  i = 0, j;
4129         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4130             if(string[i] == ' ') { ranks++; files = 0; }
4131             else files++;
4132             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4133             i++;
4134         }
4135         for(j = 0; j <i; j++) board_chars[j] = string[j];
4136         board_chars[i] = '\0';
4137         string += i + 1;
4138     }
4139     n = sscanf(string, PATTERN, &to_play, &double_push,
4140                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4141                &gamenum, white, black, &relation, &basetime, &increment,
4142                &white_stren, &black_stren, &white_time, &black_time,
4143                &moveNum, str, elapsed_time, move_str, &ics_flip,
4144                &ticking);
4145
4146     if (n < 21) {
4147         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4148         DisplayError(str, 0);
4149         return;
4150     }
4151
4152     /* Convert the move number to internal form */
4153     moveNum = (moveNum - 1) * 2;
4154     if (to_play == 'B') moveNum++;
4155     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4156       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4157                         0, 1);
4158       return;
4159     }
4160
4161     switch (relation) {
4162       case RELATION_OBSERVING_PLAYED:
4163       case RELATION_OBSERVING_STATIC:
4164         if (gamenum == -1) {
4165             /* Old ICC buglet */
4166             relation = RELATION_OBSERVING_STATIC;
4167         }
4168         newGameMode = IcsObserving;
4169         break;
4170       case RELATION_PLAYING_MYMOVE:
4171       case RELATION_PLAYING_NOTMYMOVE:
4172         newGameMode =
4173           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4174             IcsPlayingWhite : IcsPlayingBlack;
4175         break;
4176       case RELATION_EXAMINING:
4177         newGameMode = IcsExamining;
4178         break;
4179       case RELATION_ISOLATED_BOARD:
4180       default:
4181         /* Just display this board.  If user was doing something else,
4182            we will forget about it until the next board comes. */
4183         newGameMode = IcsIdle;
4184         break;
4185       case RELATION_STARTING_POSITION:
4186         newGameMode = gameMode;
4187         break;
4188     }
4189
4190     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4191          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4192       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4193       char *toSqr;
4194       for (k = 0; k < ranks; k++) {
4195         for (j = 0; j < files; j++)
4196           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4197         if(gameInfo.holdingsWidth > 1) {
4198              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4199              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4200         }
4201       }
4202       CopyBoard(partnerBoard, board);
4203       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4204         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4205         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4206       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4207       if(toSqr = strchr(str, '-')) {
4208         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4209         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4210       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4211       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4212       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4213       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4214       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4215       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4216                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4217       DisplayMessage(partnerStatus, "");
4218         partnerBoardValid = TRUE;
4219       return;
4220     }
4221
4222     /* Modify behavior for initial board display on move listing
4223        of wild games.
4224        */
4225     switch (ics_getting_history) {
4226       case H_FALSE:
4227       case H_REQUESTED:
4228         break;
4229       case H_GOT_REQ_HEADER:
4230       case H_GOT_UNREQ_HEADER:
4231         /* This is the initial position of the current game */
4232         gamenum = ics_gamenum;
4233         moveNum = 0;            /* old ICS bug workaround */
4234         if (to_play == 'B') {
4235           startedFromSetupPosition = TRUE;
4236           blackPlaysFirst = TRUE;
4237           moveNum = 1;
4238           if (forwardMostMove == 0) forwardMostMove = 1;
4239           if (backwardMostMove == 0) backwardMostMove = 1;
4240           if (currentMove == 0) currentMove = 1;
4241         }
4242         newGameMode = gameMode;
4243         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4244         break;
4245       case H_GOT_UNWANTED_HEADER:
4246         /* This is an initial board that we don't want */
4247         return;
4248       case H_GETTING_MOVES:
4249         /* Should not happen */
4250         DisplayError(_("Error gathering move list: extra board"), 0);
4251         ics_getting_history = H_FALSE;
4252         return;
4253     }
4254
4255    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4256                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4257      /* [HGM] We seem to have switched variant unexpectedly
4258       * Try to guess new variant from board size
4259       */
4260           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4261           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4262           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4263           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4264           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4265           if(!weird) newVariant = VariantNormal;
4266           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4267           /* Get a move list just to see the header, which
4268              will tell us whether this is really bug or zh */
4269           if (ics_getting_history == H_FALSE) {
4270             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4271             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4272             SendToICS(str);
4273           }
4274     }
4275
4276     /* Take action if this is the first board of a new game, or of a
4277        different game than is currently being displayed.  */
4278     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4279         relation == RELATION_ISOLATED_BOARD) {
4280
4281         /* Forget the old game and get the history (if any) of the new one */
4282         if (gameMode != BeginningOfGame) {
4283           Reset(TRUE, TRUE);
4284         }
4285         newGame = TRUE;
4286         if (appData.autoRaiseBoard) BoardToTop();
4287         prevMove = -3;
4288         if (gamenum == -1) {
4289             newGameMode = IcsIdle;
4290         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4291                    appData.getMoveList && !reqFlag) {
4292             /* Need to get game history */
4293             ics_getting_history = H_REQUESTED;
4294             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4295             SendToICS(str);
4296         }
4297
4298         /* Initially flip the board to have black on the bottom if playing
4299            black or if the ICS flip flag is set, but let the user change
4300            it with the Flip View button. */
4301         flipView = appData.autoFlipView ?
4302           (newGameMode == IcsPlayingBlack) || ics_flip :
4303           appData.flipView;
4304
4305         /* Done with values from previous mode; copy in new ones */
4306         gameMode = newGameMode;
4307         ModeHighlight();
4308         ics_gamenum = gamenum;
4309         if (gamenum == gs_gamenum) {
4310             int klen = strlen(gs_kind);
4311             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4312             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4313             gameInfo.event = StrSave(str);
4314         } else {
4315             gameInfo.event = StrSave("ICS game");
4316         }
4317         gameInfo.site = StrSave(appData.icsHost);
4318         gameInfo.date = PGNDate();
4319         gameInfo.round = StrSave("-");
4320         gameInfo.white = StrSave(white);
4321         gameInfo.black = StrSave(black);
4322         timeControl = basetime * 60 * 1000;
4323         timeControl_2 = 0;
4324         timeIncrement = increment * 1000;
4325         movesPerSession = 0;
4326         gameInfo.timeControl = TimeControlTagValue();
4327         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4328   if (appData.debugMode) {
4329     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4330     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4331     setbuf(debugFP, NULL);
4332   }
4333
4334         gameInfo.outOfBook = NULL;
4335
4336         /* Do we have the ratings? */
4337         if (strcmp(player1Name, white) == 0 &&
4338             strcmp(player2Name, black) == 0) {
4339             if (appData.debugMode)
4340               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4341                       player1Rating, player2Rating);
4342             gameInfo.whiteRating = player1Rating;
4343             gameInfo.blackRating = player2Rating;
4344         } else if (strcmp(player2Name, white) == 0 &&
4345                    strcmp(player1Name, black) == 0) {
4346             if (appData.debugMode)
4347               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4348                       player2Rating, player1Rating);
4349             gameInfo.whiteRating = player2Rating;
4350             gameInfo.blackRating = player1Rating;
4351         }
4352         player1Name[0] = player2Name[0] = NULLCHAR;
4353
4354         /* Silence shouts if requested */
4355         if (appData.quietPlay &&
4356             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4357             SendToICS(ics_prefix);
4358             SendToICS("set shout 0\n");
4359         }
4360     }
4361
4362     /* Deal with midgame name changes */
4363     if (!newGame) {
4364         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4365             if (gameInfo.white) free(gameInfo.white);
4366             gameInfo.white = StrSave(white);
4367         }
4368         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4369             if (gameInfo.black) free(gameInfo.black);
4370             gameInfo.black = StrSave(black);
4371         }
4372     }
4373
4374     /* Throw away game result if anything actually changes in examine mode */
4375     if (gameMode == IcsExamining && !newGame) {
4376         gameInfo.result = GameUnfinished;
4377         if (gameInfo.resultDetails != NULL) {
4378             free(gameInfo.resultDetails);
4379             gameInfo.resultDetails = NULL;
4380         }
4381     }
4382
4383     /* In pausing && IcsExamining mode, we ignore boards coming
4384        in if they are in a different variation than we are. */
4385     if (pauseExamInvalid) return;
4386     if (pausing && gameMode == IcsExamining) {
4387         if (moveNum <= pauseExamForwardMostMove) {
4388             pauseExamInvalid = TRUE;
4389             forwardMostMove = pauseExamForwardMostMove;
4390             return;
4391         }
4392     }
4393
4394   if (appData.debugMode) {
4395     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4396   }
4397     /* Parse the board */
4398     for (k = 0; k < ranks; k++) {
4399       for (j = 0; j < files; j++)
4400         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4401       if(gameInfo.holdingsWidth > 1) {
4402            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4403            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4404       }
4405     }
4406     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4407       board[5][BOARD_RGHT+1] = WhiteAngel;
4408       board[6][BOARD_RGHT+1] = WhiteMarshall;
4409       board[1][0] = BlackMarshall;
4410       board[2][0] = BlackAngel;
4411       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4412     }
4413     CopyBoard(boards[moveNum], board);
4414     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4415     if (moveNum == 0) {
4416         startedFromSetupPosition =
4417           !CompareBoards(board, initialPosition);
4418         if(startedFromSetupPosition)
4419             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4420     }
4421
4422     /* [HGM] Set castling rights. Take the outermost Rooks,
4423        to make it also work for FRC opening positions. Note that board12
4424        is really defective for later FRC positions, as it has no way to
4425        indicate which Rook can castle if they are on the same side of King.
4426        For the initial position we grant rights to the outermost Rooks,
4427        and remember thos rights, and we then copy them on positions
4428        later in an FRC game. This means WB might not recognize castlings with
4429        Rooks that have moved back to their original position as illegal,
4430        but in ICS mode that is not its job anyway.
4431     */
4432     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4433     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4434
4435         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4436             if(board[0][i] == WhiteRook) j = i;
4437         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4438         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4439             if(board[0][i] == WhiteRook) j = i;
4440         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4441         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4442             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4443         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4444         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4445             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4446         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4447
4448         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4449         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4450         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4451             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4452         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4453             if(board[BOARD_HEIGHT-1][k] == bKing)
4454                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4455         if(gameInfo.variant == VariantTwoKings) {
4456             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4457             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4458             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4459         }
4460     } else { int r;
4461         r = boards[moveNum][CASTLING][0] = initialRights[0];
4462         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4463         r = boards[moveNum][CASTLING][1] = initialRights[1];
4464         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4465         r = boards[moveNum][CASTLING][3] = initialRights[3];
4466         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4467         r = boards[moveNum][CASTLING][4] = initialRights[4];
4468         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4469         /* wildcastle kludge: always assume King has rights */
4470         r = boards[moveNum][CASTLING][2] = initialRights[2];
4471         r = boards[moveNum][CASTLING][5] = initialRights[5];
4472     }
4473     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4474     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4475
4476
4477     if (ics_getting_history == H_GOT_REQ_HEADER ||
4478         ics_getting_history == H_GOT_UNREQ_HEADER) {
4479         /* This was an initial position from a move list, not
4480            the current position */
4481         return;
4482     }
4483
4484     /* Update currentMove and known move number limits */
4485     newMove = newGame || moveNum > forwardMostMove;
4486
4487     if (newGame) {
4488         forwardMostMove = backwardMostMove = currentMove = moveNum;
4489         if (gameMode == IcsExamining && moveNum == 0) {
4490           /* Workaround for ICS limitation: we are not told the wild
4491              type when starting to examine a game.  But if we ask for
4492              the move list, the move list header will tell us */
4493             ics_getting_history = H_REQUESTED;
4494             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4495             SendToICS(str);
4496         }
4497     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4498                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4499 #if ZIPPY
4500         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4501         /* [HGM] applied this also to an engine that is silently watching        */
4502         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4503             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4504             gameInfo.variant == currentlyInitializedVariant) {
4505           takeback = forwardMostMove - moveNum;
4506           for (i = 0; i < takeback; i++) {
4507             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4508             SendToProgram("undo\n", &first);
4509           }
4510         }
4511 #endif
4512
4513         forwardMostMove = moveNum;
4514         if (!pausing || currentMove > forwardMostMove)
4515           currentMove = forwardMostMove;
4516     } else {
4517         /* New part of history that is not contiguous with old part */
4518         if (pausing && gameMode == IcsExamining) {
4519             pauseExamInvalid = TRUE;
4520             forwardMostMove = pauseExamForwardMostMove;
4521             return;
4522         }
4523         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4524 #if ZIPPY
4525             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4526                 // [HGM] when we will receive the move list we now request, it will be
4527                 // fed to the engine from the first move on. So if the engine is not
4528                 // in the initial position now, bring it there.
4529                 InitChessProgram(&first, 0);
4530             }
4531 #endif
4532             ics_getting_history = H_REQUESTED;
4533             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4534             SendToICS(str);
4535         }
4536         forwardMostMove = backwardMostMove = currentMove = moveNum;
4537     }
4538
4539     /* Update the clocks */
4540     if (strchr(elapsed_time, '.')) {
4541       /* Time is in ms */
4542       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4543       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4544     } else {
4545       /* Time is in seconds */
4546       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4547       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4548     }
4549
4550
4551 #if ZIPPY
4552     if (appData.zippyPlay && newGame &&
4553         gameMode != IcsObserving && gameMode != IcsIdle &&
4554         gameMode != IcsExamining)
4555       ZippyFirstBoard(moveNum, basetime, increment);
4556 #endif
4557
4558     /* Put the move on the move list, first converting
4559        to canonical algebraic form. */
4560     if (moveNum > 0) {
4561   if (appData.debugMode) {
4562     if (appData.debugMode) { int f = forwardMostMove;
4563         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4564                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4565                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4566     }
4567     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4568     fprintf(debugFP, "moveNum = %d\n", moveNum);
4569     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4570     setbuf(debugFP, NULL);
4571   }
4572         if (moveNum <= backwardMostMove) {
4573             /* We don't know what the board looked like before
4574                this move.  Punt. */
4575           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4576             strcat(parseList[moveNum - 1], " ");
4577             strcat(parseList[moveNum - 1], elapsed_time);
4578             moveList[moveNum - 1][0] = NULLCHAR;
4579         } else if (strcmp(move_str, "none") == 0) {
4580             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4581             /* Again, we don't know what the board looked like;
4582                this is really the start of the game. */
4583             parseList[moveNum - 1][0] = NULLCHAR;
4584             moveList[moveNum - 1][0] = NULLCHAR;
4585             backwardMostMove = moveNum;
4586             startedFromSetupPosition = TRUE;
4587             fromX = fromY = toX = toY = -1;
4588         } else {
4589           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4590           //                 So we parse the long-algebraic move string in stead of the SAN move
4591           int valid; char buf[MSG_SIZ], *prom;
4592
4593           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4594                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4595           // str looks something like "Q/a1-a2"; kill the slash
4596           if(str[1] == '/')
4597             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4598           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4599           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4600                 strcat(buf, prom); // long move lacks promo specification!
4601           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4602                 if(appData.debugMode)
4603                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4604                 safeStrCpy(move_str, buf, MSG_SIZ);
4605           }
4606           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4607                                 &fromX, &fromY, &toX, &toY, &promoChar)
4608                || ParseOneMove(buf, moveNum - 1, &moveType,
4609                                 &fromX, &fromY, &toX, &toY, &promoChar);
4610           // end of long SAN patch
4611           if (valid) {
4612             (void) CoordsToAlgebraic(boards[moveNum - 1],
4613                                      PosFlags(moveNum - 1),
4614                                      fromY, fromX, toY, toX, promoChar,
4615                                      parseList[moveNum-1]);
4616             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4617               case MT_NONE:
4618               case MT_STALEMATE:
4619               default:
4620                 break;
4621               case MT_CHECK:
4622                 if(gameInfo.variant != VariantShogi)
4623                     strcat(parseList[moveNum - 1], "+");
4624                 break;
4625               case MT_CHECKMATE:
4626               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4627                 strcat(parseList[moveNum - 1], "#");
4628                 break;
4629             }
4630             strcat(parseList[moveNum - 1], " ");
4631             strcat(parseList[moveNum - 1], elapsed_time);
4632             /* currentMoveString is set as a side-effect of ParseOneMove */
4633             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4634             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4635             strcat(moveList[moveNum - 1], "\n");
4636
4637             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4638                                  && gameInfo.variant != VariantGrand) // inherit info that ICS does not give from previous board
4639               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4640                 ChessSquare old, new = boards[moveNum][k][j];
4641                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4642                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4643                   if(old == new) continue;
4644                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4645                   else if(new == WhiteWazir || new == BlackWazir) {
4646                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4647                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4648                       else boards[moveNum][k][j] = old; // preserve type of Gold
4649                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4650                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4651               }
4652           } else {
4653             /* Move from ICS was illegal!?  Punt. */
4654             if (appData.debugMode) {
4655               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4656               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4657             }
4658             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4659             strcat(parseList[moveNum - 1], " ");
4660             strcat(parseList[moveNum - 1], elapsed_time);
4661             moveList[moveNum - 1][0] = NULLCHAR;
4662             fromX = fromY = toX = toY = -1;
4663           }
4664         }
4665   if (appData.debugMode) {
4666     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4667     setbuf(debugFP, NULL);
4668   }
4669
4670 #if ZIPPY
4671         /* Send move to chess program (BEFORE animating it). */
4672         if (appData.zippyPlay && !newGame && newMove &&
4673            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4674
4675             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4676                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4677                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4678                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4679                             move_str);
4680                     DisplayError(str, 0);
4681                 } else {
4682                     if (first.sendTime) {
4683                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4684                     }
4685                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4686                     if (firstMove && !bookHit) {
4687                         firstMove = FALSE;
4688                         if (first.useColors) {
4689                           SendToProgram(gameMode == IcsPlayingWhite ?
4690                                         "white\ngo\n" :
4691                                         "black\ngo\n", &first);
4692                         } else {
4693                           SendToProgram("go\n", &first);
4694                         }
4695                         first.maybeThinking = TRUE;
4696                     }
4697                 }
4698             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4699               if (moveList[moveNum - 1][0] == NULLCHAR) {
4700                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4701                 DisplayError(str, 0);
4702               } else {
4703                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4704                 SendMoveToProgram(moveNum - 1, &first);
4705               }
4706             }
4707         }
4708 #endif
4709     }
4710
4711     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4712         /* If move comes from a remote source, animate it.  If it
4713            isn't remote, it will have already been animated. */
4714         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4715             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4716         }
4717         if (!pausing && appData.highlightLastMove) {
4718             SetHighlights(fromX, fromY, toX, toY);
4719         }
4720     }
4721
4722     /* Start the clocks */
4723     whiteFlag = blackFlag = FALSE;
4724     appData.clockMode = !(basetime == 0 && increment == 0);
4725     if (ticking == 0) {
4726       ics_clock_paused = TRUE;
4727       StopClocks();
4728     } else if (ticking == 1) {
4729       ics_clock_paused = FALSE;
4730     }
4731     if (gameMode == IcsIdle ||
4732         relation == RELATION_OBSERVING_STATIC ||
4733         relation == RELATION_EXAMINING ||
4734         ics_clock_paused)
4735       DisplayBothClocks();
4736     else
4737       StartClocks();
4738
4739     /* Display opponents and material strengths */
4740     if (gameInfo.variant != VariantBughouse &&
4741         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4742         if (tinyLayout || smallLayout) {
4743             if(gameInfo.variant == VariantNormal)
4744               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4745                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4746                     basetime, increment);
4747             else
4748               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4749                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4750                     basetime, increment, (int) gameInfo.variant);
4751         } else {
4752             if(gameInfo.variant == VariantNormal)
4753               snprintf(str, MSG_SIZ, _("%s (%d) vs. %s (%d) {%d %d}"),
4754                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4755                     basetime, increment);
4756             else
4757               snprintf(str, MSG_SIZ, _("%s (%d) vs. %s (%d) {%d %d %s}"),
4758                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4759                     basetime, increment, VariantName(gameInfo.variant));
4760         }
4761         DisplayTitle(str);
4762   if (appData.debugMode) {
4763     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4764   }
4765     }
4766
4767
4768     /* Display the board */
4769     if (!pausing && !appData.noGUI) {
4770
4771       if (appData.premove)
4772           if (!gotPremove ||
4773              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4774              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4775               ClearPremoveHighlights();
4776
4777       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4778         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4779       DrawPosition(j, boards[currentMove]);
4780
4781       DisplayMove(moveNum - 1);
4782       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4783             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4784               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4785         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4786       }
4787     }
4788
4789     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4790 #if ZIPPY
4791     if(bookHit) { // [HGM] book: simulate book reply
4792         static char bookMove[MSG_SIZ]; // a bit generous?
4793
4794         programStats.nodes = programStats.depth = programStats.time =
4795         programStats.score = programStats.got_only_move = 0;
4796         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4797
4798         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4799         strcat(bookMove, bookHit);
4800         HandleMachineMove(bookMove, &first);
4801     }
4802 #endif
4803 }
4804
4805 void
4806 GetMoveListEvent ()
4807 {
4808     char buf[MSG_SIZ];
4809     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4810         ics_getting_history = H_REQUESTED;
4811         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4812         SendToICS(buf);
4813     }
4814 }
4815
4816 void
4817 AnalysisPeriodicEvent (int force)
4818 {
4819     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4820          && !force) || !appData.periodicUpdates)
4821       return;
4822
4823     /* Send . command to Crafty to collect stats */
4824     SendToProgram(".\n", &first);
4825
4826     /* Don't send another until we get a response (this makes
4827        us stop sending to old Crafty's which don't understand
4828        the "." command (sending illegal cmds resets node count & time,
4829        which looks bad)) */
4830     programStats.ok_to_send = 0;
4831 }
4832
4833 void
4834 ics_update_width (int new_width)
4835 {
4836         ics_printf("set width %d\n", new_width);
4837 }
4838
4839 void
4840 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4841 {
4842     char buf[MSG_SIZ];
4843
4844     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4845         // null move in variant where engine does not understand it (for analysis purposes)
4846         SendBoard(cps, moveNum + 1); // send position after move in stead.
4847         return;
4848     }
4849     if (cps->useUsermove) {
4850       SendToProgram("usermove ", cps);
4851     }
4852     if (cps->useSAN) {
4853       char *space;
4854       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4855         int len = space - parseList[moveNum];
4856         memcpy(buf, parseList[moveNum], len);
4857         buf[len++] = '\n';
4858         buf[len] = NULLCHAR;
4859       } else {
4860         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4861       }
4862       SendToProgram(buf, cps);
4863     } else {
4864       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4865         AlphaRank(moveList[moveNum], 4);
4866         SendToProgram(moveList[moveNum], cps);
4867         AlphaRank(moveList[moveNum], 4); // and back
4868       } else
4869       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4870        * the engine. It would be nice to have a better way to identify castle
4871        * moves here. */
4872       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4873                                                                          && cps->useOOCastle) {
4874         int fromX = moveList[moveNum][0] - AAA;
4875         int fromY = moveList[moveNum][1] - ONE;
4876         int toX = moveList[moveNum][2] - AAA;
4877         int toY = moveList[moveNum][3] - ONE;
4878         if((boards[moveNum][fromY][fromX] == WhiteKing
4879             && boards[moveNum][toY][toX] == WhiteRook)
4880            || (boards[moveNum][fromY][fromX] == BlackKing
4881                && boards[moveNum][toY][toX] == BlackRook)) {
4882           if(toX > fromX) SendToProgram("O-O\n", cps);
4883           else SendToProgram("O-O-O\n", cps);
4884         }
4885         else SendToProgram(moveList[moveNum], cps);
4886       } else
4887       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4888         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4889           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4890           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4891                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4892         } else
4893           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4894                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4895         SendToProgram(buf, cps);
4896       }
4897       else SendToProgram(moveList[moveNum], cps);
4898       /* End of additions by Tord */
4899     }
4900
4901     /* [HGM] setting up the opening has brought engine in force mode! */
4902     /*       Send 'go' if we are in a mode where machine should play. */
4903     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4904         (gameMode == TwoMachinesPlay   ||
4905 #if ZIPPY
4906          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4907 #endif
4908          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4909         SendToProgram("go\n", cps);
4910   if (appData.debugMode) {
4911     fprintf(debugFP, "(extra)\n");
4912   }
4913     }
4914     setboardSpoiledMachineBlack = 0;
4915 }
4916
4917 void
4918 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4919 {
4920     char user_move[MSG_SIZ];
4921     char suffix[4];
4922
4923     if(gameInfo.variant == VariantSChess && promoChar) {
4924         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4925         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4926     } else suffix[0] = NULLCHAR;
4927
4928     switch (moveType) {
4929       default:
4930         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4931                 (int)moveType, fromX, fromY, toX, toY);
4932         DisplayError(user_move + strlen("say "), 0);
4933         break;
4934       case WhiteKingSideCastle:
4935       case BlackKingSideCastle:
4936       case WhiteQueenSideCastleWild:
4937       case BlackQueenSideCastleWild:
4938       /* PUSH Fabien */
4939       case WhiteHSideCastleFR:
4940       case BlackHSideCastleFR:
4941       /* POP Fabien */
4942         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4943         break;
4944       case WhiteQueenSideCastle:
4945       case BlackQueenSideCastle:
4946       case WhiteKingSideCastleWild:
4947       case BlackKingSideCastleWild:
4948       /* PUSH Fabien */
4949       case WhiteASideCastleFR:
4950       case BlackASideCastleFR:
4951       /* POP Fabien */
4952         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
4953         break;
4954       case WhiteNonPromotion:
4955       case BlackNonPromotion:
4956         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4957         break;
4958       case WhitePromotion:
4959       case BlackPromotion:
4960         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4961           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4962                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4963                 PieceToChar(WhiteFerz));
4964         else if(gameInfo.variant == VariantGreat)
4965           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4966                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4967                 PieceToChar(WhiteMan));
4968         else
4969           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4970                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4971                 promoChar);
4972         break;
4973       case WhiteDrop:
4974       case BlackDrop:
4975       drop:
4976         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4977                  ToUpper(PieceToChar((ChessSquare) fromX)),
4978                  AAA + toX, ONE + toY);
4979         break;
4980       case IllegalMove:  /* could be a variant we don't quite understand */
4981         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4982       case NormalMove:
4983       case WhiteCapturesEnPassant:
4984       case BlackCapturesEnPassant:
4985         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4986                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4987         break;
4988     }
4989     SendToICS(user_move);
4990     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4991         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4992 }
4993
4994 void
4995 UploadGameEvent ()
4996 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4997     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4998     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4999     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5000       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5001       return;
5002     }
5003     if(gameMode != IcsExamining) { // is this ever not the case?
5004         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5005
5006         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5007           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5008         } else { // on FICS we must first go to general examine mode
5009           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5010         }
5011         if(gameInfo.variant != VariantNormal) {
5012             // try figure out wild number, as xboard names are not always valid on ICS
5013             for(i=1; i<=36; i++) {
5014               snprintf(buf, MSG_SIZ, "wild/%d", i);
5015                 if(StringToVariant(buf) == gameInfo.variant) break;
5016             }
5017             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5018             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5019             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5020         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5021         SendToICS(ics_prefix);
5022         SendToICS(buf);
5023         if(startedFromSetupPosition || backwardMostMove != 0) {
5024           fen = PositionToFEN(backwardMostMove, NULL);
5025           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5026             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5027             SendToICS(buf);
5028           } else { // FICS: everything has to set by separate bsetup commands
5029             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5030             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5031             SendToICS(buf);
5032             if(!WhiteOnMove(backwardMostMove)) {
5033                 SendToICS("bsetup tomove black\n");
5034             }
5035             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5036             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5037             SendToICS(buf);
5038             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5039             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5040             SendToICS(buf);
5041             i = boards[backwardMostMove][EP_STATUS];
5042             if(i >= 0) { // set e.p.
5043               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5044                 SendToICS(buf);
5045             }
5046             bsetup++;
5047           }
5048         }
5049       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5050             SendToICS("bsetup done\n"); // switch to normal examining.
5051     }
5052     for(i = backwardMostMove; i<last; i++) {
5053         char buf[20];
5054         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5055         SendToICS(buf);
5056     }
5057     SendToICS(ics_prefix);
5058     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5059 }
5060
5061 void
5062 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5063 {
5064     if (rf == DROP_RANK) {
5065       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5066       sprintf(move, "%c@%c%c\n",
5067                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5068     } else {
5069         if (promoChar == 'x' || promoChar == NULLCHAR) {
5070           sprintf(move, "%c%c%c%c\n",
5071                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5072         } else {
5073             sprintf(move, "%c%c%c%c%c\n",
5074                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5075         }
5076     }
5077 }
5078
5079 void
5080 ProcessICSInitScript (FILE *f)
5081 {
5082     char buf[MSG_SIZ];
5083
5084     while (fgets(buf, MSG_SIZ, f)) {
5085         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5086     }
5087
5088     fclose(f);
5089 }
5090
5091
5092 static int lastX, lastY, selectFlag, dragging;
5093
5094 void
5095 Sweep (int step)
5096 {
5097     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5098     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5099     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5100     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5101     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5102     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5103     do {
5104         promoSweep -= step;
5105         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5106         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5107         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5108         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5109         if(!step) step = -1;
5110     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5111             appData.testLegality && (promoSweep == king ||
5112             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5113     ChangeDragPiece(promoSweep);
5114 }
5115
5116 int
5117 PromoScroll (int x, int y)
5118 {
5119   int step = 0;
5120
5121   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5122   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5123   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5124   if(!step) return FALSE;
5125   lastX = x; lastY = y;
5126   if((promoSweep < BlackPawn) == flipView) step = -step;
5127   if(step > 0) selectFlag = 1;
5128   if(!selectFlag) Sweep(step);
5129   return FALSE;
5130 }
5131
5132 void
5133 NextPiece (int step)
5134 {
5135     ChessSquare piece = boards[currentMove][toY][toX];
5136     do {
5137         pieceSweep -= step;
5138         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5139         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5140         if(!step) step = -1;
5141     } while(PieceToChar(pieceSweep) == '.');
5142     boards[currentMove][toY][toX] = pieceSweep;
5143     DrawPosition(FALSE, boards[currentMove]);
5144     boards[currentMove][toY][toX] = piece;
5145 }
5146 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5147 void
5148 AlphaRank (char *move, int n)
5149 {
5150 //    char *p = move, c; int x, y;
5151
5152     if (appData.debugMode) {
5153         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5154     }
5155
5156     if(move[1]=='*' &&
5157        move[2]>='0' && move[2]<='9' &&
5158        move[3]>='a' && move[3]<='x'    ) {
5159         move[1] = '@';
5160         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5161         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5162     } else
5163     if(move[0]>='0' && move[0]<='9' &&
5164        move[1]>='a' && move[1]<='x' &&
5165        move[2]>='0' && move[2]<='9' &&
5166        move[3]>='a' && move[3]<='x'    ) {
5167         /* input move, Shogi -> normal */
5168         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5169         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5170         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5171         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5172     } else
5173     if(move[1]=='@' &&
5174        move[3]>='0' && move[3]<='9' &&
5175        move[2]>='a' && move[2]<='x'    ) {
5176         move[1] = '*';
5177         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5178         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5179     } else
5180     if(
5181        move[0]>='a' && move[0]<='x' &&
5182        move[3]>='0' && move[3]<='9' &&
5183        move[2]>='a' && move[2]<='x'    ) {
5184          /* output move, normal -> Shogi */
5185         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5186         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5187         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5188         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5189         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5190     }
5191     if (appData.debugMode) {
5192         fprintf(debugFP, "   out = '%s'\n", move);
5193     }
5194 }
5195
5196 char yy_textstr[8000];
5197
5198 /* Parser for moves from gnuchess, ICS, or user typein box */
5199 Boolean
5200 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5201 {
5202     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5203
5204     switch (*moveType) {
5205       case WhitePromotion:
5206       case BlackPromotion:
5207       case WhiteNonPromotion:
5208       case BlackNonPromotion:
5209       case NormalMove:
5210       case WhiteCapturesEnPassant:
5211       case BlackCapturesEnPassant:
5212       case WhiteKingSideCastle:
5213       case WhiteQueenSideCastle:
5214       case BlackKingSideCastle:
5215       case BlackQueenSideCastle:
5216       case WhiteKingSideCastleWild:
5217       case WhiteQueenSideCastleWild:
5218       case BlackKingSideCastleWild:
5219       case BlackQueenSideCastleWild:
5220       /* Code added by Tord: */
5221       case WhiteHSideCastleFR:
5222       case WhiteASideCastleFR:
5223       case BlackHSideCastleFR:
5224       case BlackASideCastleFR:
5225       /* End of code added by Tord */
5226       case IllegalMove:         /* bug or odd chess variant */
5227         *fromX = currentMoveString[0] - AAA;
5228         *fromY = currentMoveString[1] - ONE;
5229         *toX = currentMoveString[2] - AAA;
5230         *toY = currentMoveString[3] - ONE;
5231         *promoChar = currentMoveString[4];
5232         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5233             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5234     if (appData.debugMode) {
5235         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5236     }
5237             *fromX = *fromY = *toX = *toY = 0;
5238             return FALSE;
5239         }
5240         if (appData.testLegality) {
5241           return (*moveType != IllegalMove);
5242         } else {
5243           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5244                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5245         }
5246
5247       case WhiteDrop:
5248       case BlackDrop:
5249         *fromX = *moveType == WhiteDrop ?
5250           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5251           (int) CharToPiece(ToLower(currentMoveString[0]));
5252         *fromY = DROP_RANK;
5253         *toX = currentMoveString[2] - AAA;
5254         *toY = currentMoveString[3] - ONE;
5255         *promoChar = NULLCHAR;
5256         return TRUE;
5257
5258       case AmbiguousMove:
5259       case ImpossibleMove:
5260       case EndOfFile:
5261       case ElapsedTime:
5262       case Comment:
5263       case PGNTag:
5264       case NAG:
5265       case WhiteWins:
5266       case BlackWins:
5267       case GameIsDrawn:
5268       default:
5269     if (appData.debugMode) {
5270         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5271     }
5272         /* bug? */
5273         *fromX = *fromY = *toX = *toY = 0;
5274         *promoChar = NULLCHAR;
5275         return FALSE;
5276     }
5277 }
5278
5279 Boolean pushed = FALSE;
5280 char *lastParseAttempt;
5281
5282 void
5283 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5284 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5285   int fromX, fromY, toX, toY; char promoChar;
5286   ChessMove moveType;
5287   Boolean valid;
5288   int nr = 0;
5289
5290   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5291     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5292     pushed = TRUE;
5293   }
5294   endPV = forwardMostMove;
5295   do {
5296     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5297     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5298     lastParseAttempt = pv;
5299     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5300 if(appData.debugMode){
5301 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);
5302 }
5303     if(!valid && nr == 0 &&
5304        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5305         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5306         // Hande case where played move is different from leading PV move
5307         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5308         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5309         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5310         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5311           endPV += 2; // if position different, keep this
5312           moveList[endPV-1][0] = fromX + AAA;
5313           moveList[endPV-1][1] = fromY + ONE;
5314           moveList[endPV-1][2] = toX + AAA;
5315           moveList[endPV-1][3] = toY + ONE;
5316           parseList[endPV-1][0] = NULLCHAR;
5317           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5318         }
5319       }
5320     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5321     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5322     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5323     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5324         valid++; // allow comments in PV
5325         continue;
5326     }
5327     nr++;
5328     if(endPV+1 > framePtr) break; // no space, truncate
5329     if(!valid) break;
5330     endPV++;
5331     CopyBoard(boards[endPV], boards[endPV-1]);
5332     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5333     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5334     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5335     CoordsToAlgebraic(boards[endPV - 1],
5336                              PosFlags(endPV - 1),
5337                              fromY, fromX, toY, toX, promoChar,
5338                              parseList[endPV - 1]);
5339   } while(valid);
5340   if(atEnd == 2) return; // used hidden, for PV conversion
5341   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5342   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5343   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5344                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5345   DrawPosition(TRUE, boards[currentMove]);
5346 }
5347
5348 int
5349 MultiPV (ChessProgramState *cps)
5350 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5351         int i;
5352         for(i=0; i<cps->nrOptions; i++)
5353             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5354                 return i;
5355         return -1;
5356 }
5357
5358 Boolean
5359 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5360 {
5361         int startPV, multi, lineStart, origIndex = index;
5362         char *p, buf2[MSG_SIZ];
5363
5364         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5365         lastX = x; lastY = y;
5366         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5367         lineStart = startPV = index;
5368         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5369         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5370         index = startPV;
5371         do{ while(buf[index] && buf[index] != '\n') index++;
5372         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5373         buf[index] = 0;
5374         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5375                 int n = first.option[multi].value;
5376                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5377                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5378                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5379                 first.option[multi].value = n;
5380                 *start = *end = 0;
5381                 return FALSE;
5382         }
5383         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5384         *start = startPV; *end = index-1;
5385         return TRUE;
5386 }
5387
5388 char *
5389 PvToSAN (char *pv)
5390 {
5391         static char buf[10*MSG_SIZ];
5392         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5393         *buf = NULLCHAR;
5394         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5395         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5396         for(i = forwardMostMove; i<endPV; i++){
5397             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5398             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5399             k += strlen(buf+k);
5400         }
5401         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5402         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5403         endPV = savedEnd;
5404         return buf;
5405 }
5406
5407 Boolean
5408 LoadPV (int x, int y)
5409 { // called on right mouse click to load PV
5410   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5411   lastX = x; lastY = y;
5412   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5413   return TRUE;
5414 }
5415
5416 void
5417 UnLoadPV ()
5418 {
5419   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5420   if(endPV < 0) return;
5421   endPV = -1;
5422   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5423         Boolean saveAnimate = appData.animate;
5424         if(pushed) {
5425             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5426                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5427             } else storedGames--; // abandon shelved tail of original game
5428         }
5429         pushed = FALSE;
5430         forwardMostMove = currentMove;
5431         currentMove = oldFMM;
5432         appData.animate = FALSE;
5433         ToNrEvent(forwardMostMove);
5434         appData.animate = saveAnimate;
5435   }
5436   currentMove = forwardMostMove;
5437   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5438   ClearPremoveHighlights();
5439   DrawPosition(TRUE, boards[currentMove]);
5440 }
5441
5442 void
5443 MovePV (int x, int y, int h)
5444 { // step through PV based on mouse coordinates (called on mouse move)
5445   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5446
5447   // we must somehow check if right button is still down (might be released off board!)
5448   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5449   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5450   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5451   if(!step) return;
5452   lastX = x; lastY = y;
5453
5454   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5455   if(endPV < 0) return;
5456   if(y < margin) step = 1; else
5457   if(y > h - margin) step = -1;
5458   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5459   currentMove += step;
5460   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5461   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5462                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5463   DrawPosition(FALSE, boards[currentMove]);
5464 }
5465
5466
5467 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5468 // All positions will have equal probability, but the current method will not provide a unique
5469 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5470 #define DARK 1
5471 #define LITE 2
5472 #define ANY 3
5473
5474 int squaresLeft[4];
5475 int piecesLeft[(int)BlackPawn];
5476 int seed, nrOfShuffles;
5477
5478 void
5479 GetPositionNumber ()
5480 {       // sets global variable seed
5481         int i;
5482
5483         seed = appData.defaultFrcPosition;
5484         if(seed < 0) { // randomize based on time for negative FRC position numbers
5485                 for(i=0; i<50; i++) seed += random();
5486                 seed = random() ^ random() >> 8 ^ random() << 8;
5487                 if(seed<0) seed = -seed;
5488         }
5489 }
5490
5491 int
5492 put (Board board, int pieceType, int rank, int n, int shade)
5493 // put the piece on the (n-1)-th empty squares of the given shade
5494 {
5495         int i;
5496
5497         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5498                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5499                         board[rank][i] = (ChessSquare) pieceType;
5500                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5501                         squaresLeft[ANY]--;
5502                         piecesLeft[pieceType]--;
5503                         return i;
5504                 }
5505         }
5506         return -1;
5507 }
5508
5509
5510 void
5511 AddOnePiece (Board board, int pieceType, int rank, int shade)
5512 // calculate where the next piece goes, (any empty square), and put it there
5513 {
5514         int i;
5515
5516         i = seed % squaresLeft[shade];
5517         nrOfShuffles *= squaresLeft[shade];
5518         seed /= squaresLeft[shade];
5519         put(board, pieceType, rank, i, shade);
5520 }
5521
5522 void
5523 AddTwoPieces (Board board, int pieceType, int rank)
5524 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5525 {
5526         int i, n=squaresLeft[ANY], j=n-1, k;
5527
5528         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5529         i = seed % k;  // pick one
5530         nrOfShuffles *= k;
5531         seed /= k;
5532         while(i >= j) i -= j--;
5533         j = n - 1 - j; i += j;
5534         put(board, pieceType, rank, j, ANY);
5535         put(board, pieceType, rank, i, ANY);
5536 }
5537
5538 void
5539 SetUpShuffle (Board board, int number)
5540 {
5541         int i, p, first=1;
5542
5543         GetPositionNumber(); nrOfShuffles = 1;
5544
5545         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5546         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5547         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5548
5549         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5550
5551         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5552             p = (int) board[0][i];
5553             if(p < (int) BlackPawn) piecesLeft[p] ++;
5554             board[0][i] = EmptySquare;
5555         }
5556
5557         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5558             // shuffles restricted to allow normal castling put KRR first
5559             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5560                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5561             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5562                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5563             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5564                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5565             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5566                 put(board, WhiteRook, 0, 0, ANY);
5567             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5568         }
5569
5570         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5571             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5572             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5573                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5574                 while(piecesLeft[p] >= 2) {
5575                     AddOnePiece(board, p, 0, LITE);
5576                     AddOnePiece(board, p, 0, DARK);
5577                 }
5578                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5579             }
5580
5581         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5582             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5583             // but we leave King and Rooks for last, to possibly obey FRC restriction
5584             if(p == (int)WhiteRook) continue;
5585             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5586             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5587         }
5588
5589         // now everything is placed, except perhaps King (Unicorn) and Rooks
5590
5591         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5592             // Last King gets castling rights
5593             while(piecesLeft[(int)WhiteUnicorn]) {
5594                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5595                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5596             }
5597
5598             while(piecesLeft[(int)WhiteKing]) {
5599                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5600                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5601             }
5602
5603
5604         } else {
5605             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5606             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5607         }
5608
5609         // Only Rooks can be left; simply place them all
5610         while(piecesLeft[(int)WhiteRook]) {
5611                 i = put(board, WhiteRook, 0, 0, ANY);
5612                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5613                         if(first) {
5614                                 first=0;
5615                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5616                         }
5617                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5618                 }
5619         }
5620         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5621             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5622         }
5623
5624         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5625 }
5626
5627 int
5628 SetCharTable (char *table, const char * map)
5629 /* [HGM] moved here from winboard.c because of its general usefulness */
5630 /*       Basically a safe strcpy that uses the last character as King */
5631 {
5632     int result = FALSE; int NrPieces;
5633
5634     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5635                     && NrPieces >= 12 && !(NrPieces&1)) {
5636         int i; /* [HGM] Accept even length from 12 to 34 */
5637
5638         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5639         for( i=0; i<NrPieces/2-1; i++ ) {
5640             table[i] = map[i];
5641             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5642         }
5643         table[(int) WhiteKing]  = map[NrPieces/2-1];
5644         table[(int) BlackKing]  = map[NrPieces-1];
5645
5646         result = TRUE;
5647     }
5648
5649     return result;
5650 }
5651
5652 void
5653 Prelude (Board board)
5654 {       // [HGM] superchess: random selection of exo-pieces
5655         int i, j, k; ChessSquare p;
5656         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5657
5658         GetPositionNumber(); // use FRC position number
5659
5660         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5661             SetCharTable(pieceToChar, appData.pieceToCharTable);
5662             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5663                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5664         }
5665
5666         j = seed%4;                 seed /= 4;
5667         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5668         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5669         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5670         j = seed%3 + (seed%3 >= j); seed /= 3;
5671         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5672         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5673         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5674         j = seed%3;                 seed /= 3;
5675         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5676         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5677         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5678         j = seed%2 + (seed%2 >= j); seed /= 2;
5679         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5680         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5681         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5682         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5683         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5684         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5685         put(board, exoPieces[0],    0, 0, ANY);
5686         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5687 }
5688
5689 void
5690 InitPosition (int redraw)
5691 {
5692     ChessSquare (* pieces)[BOARD_FILES];
5693     int i, j, pawnRow, overrule,
5694     oldx = gameInfo.boardWidth,
5695     oldy = gameInfo.boardHeight,
5696     oldh = gameInfo.holdingsWidth;
5697     static int oldv;
5698
5699     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5700
5701     /* [AS] Initialize pv info list [HGM] and game status */
5702     {
5703         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5704             pvInfoList[i].depth = 0;
5705             boards[i][EP_STATUS] = EP_NONE;
5706             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5707         }
5708
5709         initialRulePlies = 0; /* 50-move counter start */
5710
5711         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5712         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5713     }
5714
5715
5716     /* [HGM] logic here is completely changed. In stead of full positions */
5717     /* the initialized data only consist of the two backranks. The switch */
5718     /* selects which one we will use, which is than copied to the Board   */
5719     /* initialPosition, which for the rest is initialized by Pawns and    */
5720     /* empty squares. This initial position is then copied to boards[0],  */
5721     /* possibly after shuffling, so that it remains available.            */
5722
5723     gameInfo.holdingsWidth = 0; /* default board sizes */
5724     gameInfo.boardWidth    = 8;
5725     gameInfo.boardHeight   = 8;
5726     gameInfo.holdingsSize  = 0;
5727     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5728     for(i=0; i<BOARD_FILES-2; i++)
5729       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5730     initialPosition[EP_STATUS] = EP_NONE;
5731     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5732     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5733          SetCharTable(pieceNickName, appData.pieceNickNames);
5734     else SetCharTable(pieceNickName, "............");
5735     pieces = FIDEArray;
5736
5737     switch (gameInfo.variant) {
5738     case VariantFischeRandom:
5739       shuffleOpenings = TRUE;
5740     default:
5741       break;
5742     case VariantShatranj:
5743       pieces = ShatranjArray;
5744       nrCastlingRights = 0;
5745       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5746       break;
5747     case VariantMakruk:
5748       pieces = makrukArray;
5749       nrCastlingRights = 0;
5750       startedFromSetupPosition = TRUE;
5751       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5752       break;
5753     case VariantTwoKings:
5754       pieces = twoKingsArray;
5755       break;
5756     case VariantGrand:
5757       pieces = GrandArray;
5758       nrCastlingRights = 0;
5759       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5760       gameInfo.boardWidth = 10;
5761       gameInfo.boardHeight = 10;
5762       gameInfo.holdingsSize = 7;
5763       break;
5764     case VariantCapaRandom:
5765       shuffleOpenings = TRUE;
5766     case VariantCapablanca:
5767       pieces = CapablancaArray;
5768       gameInfo.boardWidth = 10;
5769       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5770       break;
5771     case VariantGothic:
5772       pieces = GothicArray;
5773       gameInfo.boardWidth = 10;
5774       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5775       break;
5776     case VariantSChess:
5777       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5778       gameInfo.holdingsSize = 7;
5779       break;
5780     case VariantJanus:
5781       pieces = JanusArray;
5782       gameInfo.boardWidth = 10;
5783       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5784       nrCastlingRights = 6;
5785         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5786         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5787         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5788         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5789         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5790         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5791       break;
5792     case VariantFalcon:
5793       pieces = FalconArray;
5794       gameInfo.boardWidth = 10;
5795       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5796       break;
5797     case VariantXiangqi:
5798       pieces = XiangqiArray;
5799       gameInfo.boardWidth  = 9;
5800       gameInfo.boardHeight = 10;
5801       nrCastlingRights = 0;
5802       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5803       break;
5804     case VariantShogi:
5805       pieces = ShogiArray;
5806       gameInfo.boardWidth  = 9;
5807       gameInfo.boardHeight = 9;
5808       gameInfo.holdingsSize = 7;
5809       nrCastlingRights = 0;
5810       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5811       break;
5812     case VariantCourier:
5813       pieces = CourierArray;
5814       gameInfo.boardWidth  = 12;
5815       nrCastlingRights = 0;
5816       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5817       break;
5818     case VariantKnightmate:
5819       pieces = KnightmateArray;
5820       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5821       break;
5822     case VariantSpartan:
5823       pieces = SpartanArray;
5824       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5825       break;
5826     case VariantFairy:
5827       pieces = fairyArray;
5828       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5829       break;
5830     case VariantGreat:
5831       pieces = GreatArray;
5832       gameInfo.boardWidth = 10;
5833       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5834       gameInfo.holdingsSize = 8;
5835       break;
5836     case VariantSuper:
5837       pieces = FIDEArray;
5838       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5839       gameInfo.holdingsSize = 8;
5840       startedFromSetupPosition = TRUE;
5841       break;
5842     case VariantCrazyhouse:
5843     case VariantBughouse:
5844       pieces = FIDEArray;
5845       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5846       gameInfo.holdingsSize = 5;
5847       break;
5848     case VariantWildCastle:
5849       pieces = FIDEArray;
5850       /* !!?shuffle with kings guaranteed to be on d or e file */
5851       shuffleOpenings = 1;
5852       break;
5853     case VariantNoCastle:
5854       pieces = FIDEArray;
5855       nrCastlingRights = 0;
5856       /* !!?unconstrained back-rank shuffle */
5857       shuffleOpenings = 1;
5858       break;
5859     }
5860
5861     overrule = 0;
5862     if(appData.NrFiles >= 0) {
5863         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5864         gameInfo.boardWidth = appData.NrFiles;
5865     }
5866     if(appData.NrRanks >= 0) {
5867         gameInfo.boardHeight = appData.NrRanks;
5868     }
5869     if(appData.holdingsSize >= 0) {
5870         i = appData.holdingsSize;
5871         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5872         gameInfo.holdingsSize = i;
5873     }
5874     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5875     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5876         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5877
5878     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5879     if(pawnRow < 1) pawnRow = 1;
5880     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5881
5882     /* User pieceToChar list overrules defaults */
5883     if(appData.pieceToCharTable != NULL)
5884         SetCharTable(pieceToChar, appData.pieceToCharTable);
5885
5886     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5887
5888         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5889             s = (ChessSquare) 0; /* account holding counts in guard band */
5890         for( i=0; i<BOARD_HEIGHT; i++ )
5891             initialPosition[i][j] = s;
5892
5893         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5894         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5895         initialPosition[pawnRow][j] = WhitePawn;
5896         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5897         if(gameInfo.variant == VariantXiangqi) {
5898             if(j&1) {
5899                 initialPosition[pawnRow][j] =
5900                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5901                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5902                    initialPosition[2][j] = WhiteCannon;
5903                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5904                 }
5905             }
5906         }
5907         if(gameInfo.variant == VariantGrand) {
5908             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5909                initialPosition[0][j] = WhiteRook;
5910                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5911             }
5912         }
5913         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5914     }
5915     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5916
5917             j=BOARD_LEFT+1;
5918             initialPosition[1][j] = WhiteBishop;
5919             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5920             j=BOARD_RGHT-2;
5921             initialPosition[1][j] = WhiteRook;
5922             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5923     }
5924
5925     if( nrCastlingRights == -1) {
5926         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5927         /*       This sets default castling rights from none to normal corners   */
5928         /* Variants with other castling rights must set them themselves above    */
5929         nrCastlingRights = 6;
5930
5931         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5932         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5933         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5934         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5935         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5936         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5937      }
5938
5939      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5940      if(gameInfo.variant == VariantGreat) { // promotion commoners
5941         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5942         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5943         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5944         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5945      }
5946      if( gameInfo.variant == VariantSChess ) {
5947       initialPosition[1][0] = BlackMarshall;
5948       initialPosition[2][0] = BlackAngel;
5949       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5950       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5951       initialPosition[1][1] = initialPosition[2][1] = 
5952       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5953      }
5954   if (appData.debugMode) {
5955     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5956   }
5957     if(shuffleOpenings) {
5958         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5959         startedFromSetupPosition = TRUE;
5960     }
5961     if(startedFromPositionFile) {
5962       /* [HGM] loadPos: use PositionFile for every new game */
5963       CopyBoard(initialPosition, filePosition);
5964       for(i=0; i<nrCastlingRights; i++)
5965           initialRights[i] = filePosition[CASTLING][i];
5966       startedFromSetupPosition = TRUE;
5967     }
5968
5969     CopyBoard(boards[0], initialPosition);
5970
5971     if(oldx != gameInfo.boardWidth ||
5972        oldy != gameInfo.boardHeight ||
5973        oldv != gameInfo.variant ||
5974        oldh != gameInfo.holdingsWidth
5975                                          )
5976             InitDrawingSizes(-2 ,0);
5977
5978     oldv = gameInfo.variant;
5979     if (redraw)
5980       DrawPosition(TRUE, boards[currentMove]);
5981 }
5982
5983 void
5984 SendBoard (ChessProgramState *cps, int moveNum)
5985 {
5986     char message[MSG_SIZ];
5987
5988     if (cps->useSetboard) {
5989       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5990       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5991       SendToProgram(message, cps);
5992       free(fen);
5993
5994     } else {
5995       ChessSquare *bp;
5996       int i, j, left=0, right=BOARD_WIDTH;
5997       /* Kludge to set black to move, avoiding the troublesome and now
5998        * deprecated "black" command.
5999        */
6000       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6001         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6002
6003       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6004
6005       SendToProgram("edit\n", cps);
6006       SendToProgram("#\n", cps);
6007       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6008         bp = &boards[moveNum][i][left];
6009         for (j = left; j < right; j++, bp++) {
6010           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6011           if ((int) *bp < (int) BlackPawn) {
6012             if(j == BOARD_RGHT+1)
6013                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6014             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6015             if(message[0] == '+' || message[0] == '~') {
6016               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6017                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6018                         AAA + j, ONE + i);
6019             }
6020             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6021                 message[1] = BOARD_RGHT   - 1 - j + '1';
6022                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6023             }
6024             SendToProgram(message, cps);
6025           }
6026         }
6027       }
6028
6029       SendToProgram("c\n", cps);
6030       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6031         bp = &boards[moveNum][i][left];
6032         for (j = left; j < right; j++, bp++) {
6033           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6034           if (((int) *bp != (int) EmptySquare)
6035               && ((int) *bp >= (int) BlackPawn)) {
6036             if(j == BOARD_LEFT-2)
6037                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6038             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6039                     AAA + j, ONE + i);
6040             if(message[0] == '+' || message[0] == '~') {
6041               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6042                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6043                         AAA + j, ONE + i);
6044             }
6045             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6046                 message[1] = BOARD_RGHT   - 1 - j + '1';
6047                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6048             }
6049             SendToProgram(message, cps);
6050           }
6051         }
6052       }
6053
6054       SendToProgram(".\n", cps);
6055     }
6056     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6057 }
6058
6059 ChessSquare
6060 DefaultPromoChoice (int white)
6061 {
6062     ChessSquare result;
6063     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6064         result = WhiteFerz; // no choice
6065     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6066         result= WhiteKing; // in Suicide Q is the last thing we want
6067     else if(gameInfo.variant == VariantSpartan)
6068         result = white ? WhiteQueen : WhiteAngel;
6069     else result = WhiteQueen;
6070     if(!white) result = WHITE_TO_BLACK result;
6071     return result;
6072 }
6073
6074 static int autoQueen; // [HGM] oneclick
6075
6076 int
6077 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6078 {
6079     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6080     /* [HGM] add Shogi promotions */
6081     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6082     ChessSquare piece;
6083     ChessMove moveType;
6084     Boolean premove;
6085
6086     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6087     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6088
6089     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6090       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6091         return FALSE;
6092
6093     piece = boards[currentMove][fromY][fromX];
6094     if(gameInfo.variant == VariantShogi) {
6095         promotionZoneSize = BOARD_HEIGHT/3;
6096         highestPromotingPiece = (int)WhiteFerz;
6097     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6098         promotionZoneSize = 3;
6099     }
6100
6101     // Treat Lance as Pawn when it is not representing Amazon
6102     if(gameInfo.variant != VariantSuper) {
6103         if(piece == WhiteLance) piece = WhitePawn; else
6104         if(piece == BlackLance) piece = BlackPawn;
6105     }
6106
6107     // next weed out all moves that do not touch the promotion zone at all
6108     if((int)piece >= BlackPawn) {
6109         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6110              return FALSE;
6111         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6112     } else {
6113         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6114            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6115     }
6116
6117     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6118
6119     // weed out mandatory Shogi promotions
6120     if(gameInfo.variant == VariantShogi) {
6121         if(piece >= BlackPawn) {
6122             if(toY == 0 && piece == BlackPawn ||
6123                toY == 0 && piece == BlackQueen ||
6124                toY <= 1 && piece == BlackKnight) {
6125                 *promoChoice = '+';
6126                 return FALSE;
6127             }
6128         } else {
6129             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6130                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6131                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6132                 *promoChoice = '+';
6133                 return FALSE;
6134             }
6135         }
6136     }
6137
6138     // weed out obviously illegal Pawn moves
6139     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6140         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6141         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6142         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6143         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6144         // note we are not allowed to test for valid (non-)capture, due to premove
6145     }
6146
6147     // we either have a choice what to promote to, or (in Shogi) whether to promote
6148     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6149         *promoChoice = PieceToChar(BlackFerz);  // no choice
6150         return FALSE;
6151     }
6152     // no sense asking what we must promote to if it is going to explode...
6153     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6154         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6155         return FALSE;
6156     }
6157     // give caller the default choice even if we will not make it
6158     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6159     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6160     if(        sweepSelect && gameInfo.variant != VariantGreat
6161                            && gameInfo.variant != VariantGrand
6162                            && gameInfo.variant != VariantSuper) return FALSE;
6163     if(autoQueen) return FALSE; // predetermined
6164
6165     // suppress promotion popup on illegal moves that are not premoves
6166     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6167               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6168     if(appData.testLegality && !premove) {
6169         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6170                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6171         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6172             return FALSE;
6173     }
6174
6175     return TRUE;
6176 }
6177
6178 int
6179 InPalace (int row, int column)
6180 {   /* [HGM] for Xiangqi */
6181     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6182          column < (BOARD_WIDTH + 4)/2 &&
6183          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6184     return FALSE;
6185 }
6186
6187 int
6188 PieceForSquare (int x, int y)
6189 {
6190   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6191      return -1;
6192   else
6193      return boards[currentMove][y][x];
6194 }
6195
6196 int
6197 OKToStartUserMove (int x, int y)
6198 {
6199     ChessSquare from_piece;
6200     int white_piece;
6201
6202     if (matchMode) return FALSE;
6203     if (gameMode == EditPosition) return TRUE;
6204
6205     if (x >= 0 && y >= 0)
6206       from_piece = boards[currentMove][y][x];
6207     else
6208       from_piece = EmptySquare;
6209
6210     if (from_piece == EmptySquare) return FALSE;
6211
6212     white_piece = (int)from_piece >= (int)WhitePawn &&
6213       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6214
6215     switch (gameMode) {
6216       case AnalyzeFile:
6217       case TwoMachinesPlay:
6218       case EndOfGame:
6219         return FALSE;
6220
6221       case IcsObserving:
6222       case IcsIdle:
6223         return FALSE;
6224
6225       case MachinePlaysWhite:
6226       case IcsPlayingBlack:
6227         if (appData.zippyPlay) return FALSE;
6228         if (white_piece) {
6229             DisplayMoveError(_("You are playing Black"));
6230             return FALSE;
6231         }
6232         break;
6233
6234       case MachinePlaysBlack:
6235       case IcsPlayingWhite:
6236         if (appData.zippyPlay) return FALSE;
6237         if (!white_piece) {
6238             DisplayMoveError(_("You are playing White"));
6239             return FALSE;
6240         }
6241         break;
6242
6243       case PlayFromGameFile:
6244             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6245       case EditGame:
6246         if (!white_piece && WhiteOnMove(currentMove)) {
6247             DisplayMoveError(_("It is White's turn"));
6248             return FALSE;
6249         }
6250         if (white_piece && !WhiteOnMove(currentMove)) {
6251             DisplayMoveError(_("It is Black's turn"));
6252             return FALSE;
6253         }
6254         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6255             /* Editing correspondence game history */
6256             /* Could disallow this or prompt for confirmation */
6257             cmailOldMove = -1;
6258         }
6259         break;
6260
6261       case BeginningOfGame:
6262         if (appData.icsActive) return FALSE;
6263         if (!appData.noChessProgram) {
6264             if (!white_piece) {
6265                 DisplayMoveError(_("You are playing White"));
6266                 return FALSE;
6267             }
6268         }
6269         break;
6270
6271       case Training:
6272         if (!white_piece && WhiteOnMove(currentMove)) {
6273             DisplayMoveError(_("It is White's turn"));
6274             return FALSE;
6275         }
6276         if (white_piece && !WhiteOnMove(currentMove)) {
6277             DisplayMoveError(_("It is Black's turn"));
6278             return FALSE;
6279         }
6280         break;
6281
6282       default:
6283       case IcsExamining:
6284         break;
6285     }
6286     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6287         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6288         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6289         && gameMode != AnalyzeFile && gameMode != Training) {
6290         DisplayMoveError(_("Displayed position is not current"));
6291         return FALSE;
6292     }
6293     return TRUE;
6294 }
6295
6296 Boolean
6297 OnlyMove (int *x, int *y, Boolean captures) 
6298 {
6299     DisambiguateClosure cl;
6300     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6301     switch(gameMode) {
6302       case MachinePlaysBlack:
6303       case IcsPlayingWhite:
6304       case BeginningOfGame:
6305         if(!WhiteOnMove(currentMove)) return FALSE;
6306         break;
6307       case MachinePlaysWhite:
6308       case IcsPlayingBlack:
6309         if(WhiteOnMove(currentMove)) return FALSE;
6310         break;
6311       case EditGame:
6312         break;
6313       default:
6314         return FALSE;
6315     }
6316     cl.pieceIn = EmptySquare;
6317     cl.rfIn = *y;
6318     cl.ffIn = *x;
6319     cl.rtIn = -1;
6320     cl.ftIn = -1;
6321     cl.promoCharIn = NULLCHAR;
6322     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6323     if( cl.kind == NormalMove ||
6324         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6325         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6326         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6327       fromX = cl.ff;
6328       fromY = cl.rf;
6329       *x = cl.ft;
6330       *y = cl.rt;
6331       return TRUE;
6332     }
6333     if(cl.kind != ImpossibleMove) return FALSE;
6334     cl.pieceIn = EmptySquare;
6335     cl.rfIn = -1;
6336     cl.ffIn = -1;
6337     cl.rtIn = *y;
6338     cl.ftIn = *x;
6339     cl.promoCharIn = NULLCHAR;
6340     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6341     if( cl.kind == NormalMove ||
6342         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6343         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6344         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6345       fromX = cl.ff;
6346       fromY = cl.rf;
6347       *x = cl.ft;
6348       *y = cl.rt;
6349       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6350       return TRUE;
6351     }
6352     return FALSE;
6353 }
6354
6355 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6356 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6357 int lastLoadGameUseList = FALSE;
6358 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6359 ChessMove lastLoadGameStart = EndOfFile;
6360
6361 void
6362 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6363 {
6364     ChessMove moveType;
6365     ChessSquare pdown, pup;
6366
6367     /* Check if the user is playing in turn.  This is complicated because we
6368        let the user "pick up" a piece before it is his turn.  So the piece he
6369        tried to pick up may have been captured by the time he puts it down!
6370        Therefore we use the color the user is supposed to be playing in this
6371        test, not the color of the piece that is currently on the starting
6372        square---except in EditGame mode, where the user is playing both
6373        sides; fortunately there the capture race can't happen.  (It can
6374        now happen in IcsExamining mode, but that's just too bad.  The user
6375        will get a somewhat confusing message in that case.)
6376        */
6377
6378     switch (gameMode) {
6379       case AnalyzeFile:
6380       case TwoMachinesPlay:
6381       case EndOfGame:
6382       case IcsObserving:
6383       case IcsIdle:
6384         /* We switched into a game mode where moves are not accepted,
6385            perhaps while the mouse button was down. */
6386         return;
6387
6388       case MachinePlaysWhite:
6389         /* User is moving for Black */
6390         if (WhiteOnMove(currentMove)) {
6391             DisplayMoveError(_("It is White's turn"));
6392             return;
6393         }
6394         break;
6395
6396       case MachinePlaysBlack:
6397         /* User is moving for White */
6398         if (!WhiteOnMove(currentMove)) {
6399             DisplayMoveError(_("It is Black's turn"));
6400             return;
6401         }
6402         break;
6403
6404       case PlayFromGameFile:
6405             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6406       case EditGame:
6407       case IcsExamining:
6408       case BeginningOfGame:
6409       case AnalyzeMode:
6410       case Training:
6411         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6412         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6413             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6414             /* User is moving for Black */
6415             if (WhiteOnMove(currentMove)) {
6416                 DisplayMoveError(_("It is White's turn"));
6417                 return;
6418             }
6419         } else {
6420             /* User is moving for White */
6421             if (!WhiteOnMove(currentMove)) {
6422                 DisplayMoveError(_("It is Black's turn"));
6423                 return;
6424             }
6425         }
6426         break;
6427
6428       case IcsPlayingBlack:
6429         /* User is moving for Black */
6430         if (WhiteOnMove(currentMove)) {
6431             if (!appData.premove) {
6432                 DisplayMoveError(_("It is White's turn"));
6433             } else if (toX >= 0 && toY >= 0) {
6434                 premoveToX = toX;
6435                 premoveToY = toY;
6436                 premoveFromX = fromX;
6437                 premoveFromY = fromY;
6438                 premovePromoChar = promoChar;
6439                 gotPremove = 1;
6440                 if (appData.debugMode)
6441                     fprintf(debugFP, "Got premove: fromX %d,"
6442                             "fromY %d, toX %d, toY %d\n",
6443                             fromX, fromY, toX, toY);
6444             }
6445             return;
6446         }
6447         break;
6448
6449       case IcsPlayingWhite:
6450         /* User is moving for White */
6451         if (!WhiteOnMove(currentMove)) {
6452             if (!appData.premove) {
6453                 DisplayMoveError(_("It is Black's turn"));
6454             } else if (toX >= 0 && toY >= 0) {
6455                 premoveToX = toX;
6456                 premoveToY = toY;
6457                 premoveFromX = fromX;
6458                 premoveFromY = fromY;
6459                 premovePromoChar = promoChar;
6460                 gotPremove = 1;
6461                 if (appData.debugMode)
6462                     fprintf(debugFP, "Got premove: fromX %d,"
6463                             "fromY %d, toX %d, toY %d\n",
6464                             fromX, fromY, toX, toY);
6465             }
6466             return;
6467         }
6468         break;
6469
6470       default:
6471         break;
6472
6473       case EditPosition:
6474         /* EditPosition, empty square, or different color piece;
6475            click-click move is possible */
6476         if (toX == -2 || toY == -2) {
6477             boards[0][fromY][fromX] = EmptySquare;
6478             DrawPosition(FALSE, boards[currentMove]);
6479             return;
6480         } else if (toX >= 0 && toY >= 0) {
6481             boards[0][toY][toX] = boards[0][fromY][fromX];
6482             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6483                 if(boards[0][fromY][0] != EmptySquare) {
6484                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6485                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6486                 }
6487             } else
6488             if(fromX == BOARD_RGHT+1) {
6489                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6490                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6491                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6492                 }
6493             } else
6494             boards[0][fromY][fromX] = EmptySquare;
6495             DrawPosition(FALSE, boards[currentMove]);
6496             return;
6497         }
6498         return;
6499     }
6500
6501     if(toX < 0 || toY < 0) return;
6502     pdown = boards[currentMove][fromY][fromX];
6503     pup = boards[currentMove][toY][toX];
6504
6505     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6506     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6507          if( pup != EmptySquare ) return;
6508          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6509            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6510                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6511            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6512            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6513            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6514            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6515          fromY = DROP_RANK;
6516     }
6517
6518     /* [HGM] always test for legality, to get promotion info */
6519     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6520                                          fromY, fromX, toY, toX, promoChar);
6521
6522     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6523
6524     /* [HGM] but possibly ignore an IllegalMove result */
6525     if (appData.testLegality) {
6526         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6527             DisplayMoveError(_("Illegal move"));
6528             return;
6529         }
6530     }
6531
6532     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6533 }
6534
6535 /* Common tail of UserMoveEvent and DropMenuEvent */
6536 int
6537 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6538 {
6539     char *bookHit = 0;
6540
6541     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6542         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6543         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6544         if(WhiteOnMove(currentMove)) {
6545             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6546         } else {
6547             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6548         }
6549     }
6550
6551     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6552        move type in caller when we know the move is a legal promotion */
6553     if(moveType == NormalMove && promoChar)
6554         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6555
6556     /* [HGM] <popupFix> The following if has been moved here from
6557        UserMoveEvent(). Because it seemed to belong here (why not allow
6558        piece drops in training games?), and because it can only be
6559        performed after it is known to what we promote. */
6560     if (gameMode == Training) {
6561       /* compare the move played on the board to the next move in the
6562        * game. If they match, display the move and the opponent's response.
6563        * If they don't match, display an error message.
6564        */
6565       int saveAnimate;
6566       Board testBoard;
6567       CopyBoard(testBoard, boards[currentMove]);
6568       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6569
6570       if (CompareBoards(testBoard, boards[currentMove+1])) {
6571         ForwardInner(currentMove+1);
6572
6573         /* Autoplay the opponent's response.
6574          * if appData.animate was TRUE when Training mode was entered,
6575          * the response will be animated.
6576          */
6577         saveAnimate = appData.animate;
6578         appData.animate = animateTraining;
6579         ForwardInner(currentMove+1);
6580         appData.animate = saveAnimate;
6581
6582         /* check for the end of the game */
6583         if (currentMove >= forwardMostMove) {
6584           gameMode = PlayFromGameFile;
6585           ModeHighlight();
6586           SetTrainingModeOff();
6587           DisplayInformation(_("End of game"));
6588         }
6589       } else {
6590         DisplayError(_("Incorrect move"), 0);
6591       }
6592       return 1;
6593     }
6594
6595   /* Ok, now we know that the move is good, so we can kill
6596      the previous line in Analysis Mode */
6597   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6598                                 && currentMove < forwardMostMove) {
6599     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6600     else forwardMostMove = currentMove;
6601   }
6602
6603   /* If we need the chess program but it's dead, restart it */
6604   ResurrectChessProgram();
6605
6606   /* A user move restarts a paused game*/
6607   if (pausing)
6608     PauseEvent();
6609
6610   thinkOutput[0] = NULLCHAR;
6611
6612   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6613
6614   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6615     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6616     return 1;
6617   }
6618
6619   if (gameMode == BeginningOfGame) {
6620     if (appData.noChessProgram) {
6621       gameMode = EditGame;
6622       SetGameInfo();
6623     } else {
6624       char buf[MSG_SIZ];
6625       gameMode = MachinePlaysBlack;
6626       StartClocks();
6627       SetGameInfo();
6628       snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
6629       DisplayTitle(buf);
6630       if (first.sendName) {
6631         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6632         SendToProgram(buf, &first);
6633       }
6634       StartClocks();
6635     }
6636     ModeHighlight();
6637   }
6638
6639   /* Relay move to ICS or chess engine */
6640   if (appData.icsActive) {
6641     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6642         gameMode == IcsExamining) {
6643       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6644         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6645         SendToICS("draw ");
6646         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6647       }
6648       // also send plain move, in case ICS does not understand atomic claims
6649       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6650       ics_user_moved = 1;
6651     }
6652   } else {
6653     if (first.sendTime && (gameMode == BeginningOfGame ||
6654                            gameMode == MachinePlaysWhite ||
6655                            gameMode == MachinePlaysBlack)) {
6656       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6657     }
6658     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6659          // [HGM] book: if program might be playing, let it use book
6660         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6661         first.maybeThinking = TRUE;
6662     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6663         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6664         SendBoard(&first, currentMove+1);
6665     } else SendMoveToProgram(forwardMostMove-1, &first);
6666     if (currentMove == cmailOldMove + 1) {
6667       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6668     }
6669   }
6670
6671   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6672
6673   switch (gameMode) {
6674   case EditGame:
6675     if(appData.testLegality)
6676     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6677     case MT_NONE:
6678     case MT_CHECK:
6679       break;
6680     case MT_CHECKMATE:
6681     case MT_STAINMATE:
6682       if (WhiteOnMove(currentMove)) {
6683         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6684       } else {
6685         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6686       }
6687       break;
6688     case MT_STALEMATE:
6689       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6690       break;
6691     }
6692     break;
6693
6694   case MachinePlaysBlack:
6695   case MachinePlaysWhite:
6696     /* disable certain menu options while machine is thinking */
6697     SetMachineThinkingEnables();
6698     break;
6699
6700   default:
6701     break;
6702   }
6703
6704   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6705   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6706
6707   if(bookHit) { // [HGM] book: simulate book reply
6708         static char bookMove[MSG_SIZ]; // a bit generous?
6709
6710         programStats.nodes = programStats.depth = programStats.time =
6711         programStats.score = programStats.got_only_move = 0;
6712         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6713
6714         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6715         strcat(bookMove, bookHit);
6716         HandleMachineMove(bookMove, &first);
6717   }
6718   return 1;
6719 }
6720
6721 void
6722 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6723 {
6724     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6725     Markers *m = (Markers *) closure;
6726     if(rf == fromY && ff == fromX)
6727         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6728                          || kind == WhiteCapturesEnPassant
6729                          || kind == BlackCapturesEnPassant);
6730     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6731 }
6732
6733 void
6734 MarkTargetSquares (int clear)
6735 {
6736   int x, y;
6737   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6738      !appData.testLegality || gameMode == EditPosition) return;
6739   if(clear) {
6740     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6741   } else {
6742     int capt = 0;
6743     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6744     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6745       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6746       if(capt)
6747       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6748     }
6749   }
6750   DrawPosition(TRUE, NULL);
6751 }
6752
6753 int
6754 Explode (Board board, int fromX, int fromY, int toX, int toY)
6755 {
6756     if(gameInfo.variant == VariantAtomic &&
6757        (board[toY][toX] != EmptySquare ||                     // capture?
6758         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6759                          board[fromY][fromX] == BlackPawn   )
6760       )) {
6761         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6762         return TRUE;
6763     }
6764     return FALSE;
6765 }
6766
6767 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6768
6769 int
6770 CanPromote (ChessSquare piece, int y)
6771 {
6772         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6773         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6774         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6775            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6776            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6777                                                   gameInfo.variant == VariantMakruk) return FALSE;
6778         return (piece == BlackPawn && y == 1 ||
6779                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6780                 piece == BlackLance && y == 1 ||
6781                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6782 }
6783
6784 void
6785 LeftClick (ClickType clickType, int xPix, int yPix)
6786 {
6787     int x, y;
6788     Boolean saveAnimate;
6789     static int second = 0, promotionChoice = 0, clearFlag = 0;
6790     char promoChoice = NULLCHAR;
6791     ChessSquare piece;
6792
6793     if(appData.seekGraph && appData.icsActive && loggedOn &&
6794         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6795         SeekGraphClick(clickType, xPix, yPix, 0);
6796         return;
6797     }
6798
6799     if (clickType == Press) ErrorPopDown();
6800
6801     x = EventToSquare(xPix, BOARD_WIDTH);
6802     y = EventToSquare(yPix, BOARD_HEIGHT);
6803     if (!flipView && y >= 0) {
6804         y = BOARD_HEIGHT - 1 - y;
6805     }
6806     if (flipView && x >= 0) {
6807         x = BOARD_WIDTH - 1 - x;
6808     }
6809
6810     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6811         defaultPromoChoice = promoSweep;
6812         promoSweep = EmptySquare;   // terminate sweep
6813         promoDefaultAltered = TRUE;
6814         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6815     }
6816
6817     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6818         if(clickType == Release) return; // ignore upclick of click-click destination
6819         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6820         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6821         if(gameInfo.holdingsWidth &&
6822                 (WhiteOnMove(currentMove)
6823                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6824                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6825             // click in right holdings, for determining promotion piece
6826             ChessSquare p = boards[currentMove][y][x];
6827             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6828             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6829             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6830                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6831                 fromX = fromY = -1;
6832                 return;
6833             }
6834         }
6835         DrawPosition(FALSE, boards[currentMove]);
6836         return;
6837     }
6838
6839     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6840     if(clickType == Press
6841             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6842               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6843               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6844         return;
6845
6846     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6847         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6848
6849     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6850         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6851                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6852         defaultPromoChoice = DefaultPromoChoice(side);
6853     }
6854
6855     autoQueen = appData.alwaysPromoteToQueen;
6856
6857     if (fromX == -1) {
6858       int originalY = y;
6859       gatingPiece = EmptySquare;
6860       if (clickType != Press) {
6861         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6862             DragPieceEnd(xPix, yPix); dragging = 0;
6863             DrawPosition(FALSE, NULL);
6864         }
6865         return;
6866       }
6867       fromX = x; fromY = y; toX = toY = -1;
6868       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6869          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6870          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6871             /* First square */
6872             if (OKToStartUserMove(fromX, fromY)) {
6873                 second = 0;
6874                 MarkTargetSquares(0);
6875                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6876                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6877                     promoSweep = defaultPromoChoice;
6878                     selectFlag = 0; lastX = xPix; lastY = yPix;
6879                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6880                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6881                 }
6882                 if (appData.highlightDragging) {
6883                     SetHighlights(fromX, fromY, -1, -1);
6884                 }
6885             } else fromX = fromY = -1;
6886             return;
6887         }
6888     }
6889
6890     /* fromX != -1 */
6891     if (clickType == Press && gameMode != EditPosition) {
6892         ChessSquare fromP;
6893         ChessSquare toP;
6894         int frc;
6895
6896         // ignore off-board to clicks
6897         if(y < 0 || x < 0) return;
6898
6899         /* Check if clicking again on the same color piece */
6900         fromP = boards[currentMove][fromY][fromX];
6901         toP = boards[currentMove][y][x];
6902         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6903         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6904              WhitePawn <= toP && toP <= WhiteKing &&
6905              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6906              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6907             (BlackPawn <= fromP && fromP <= BlackKing &&
6908              BlackPawn <= toP && toP <= BlackKing &&
6909              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6910              !(fromP == BlackKing && toP == BlackRook && frc))) {
6911             /* Clicked again on same color piece -- changed his mind */
6912             second = (x == fromX && y == fromY);
6913             promoDefaultAltered = FALSE;
6914             MarkTargetSquares(1);
6915            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6916             if (appData.highlightDragging) {
6917                 SetHighlights(x, y, -1, -1);
6918             } else {
6919                 ClearHighlights();
6920             }
6921             if (OKToStartUserMove(x, y)) {
6922                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6923                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6924                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6925                  gatingPiece = boards[currentMove][fromY][fromX];
6926                 else gatingPiece = EmptySquare;
6927                 fromX = x;
6928                 fromY = y; dragging = 1;
6929                 MarkTargetSquares(0);
6930                 DragPieceBegin(xPix, yPix, FALSE);
6931                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6932                     promoSweep = defaultPromoChoice;
6933                     selectFlag = 0; lastX = xPix; lastY = yPix;
6934                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6935                 }
6936             }
6937            }
6938            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6939            second = FALSE; 
6940         }
6941         // ignore clicks on holdings
6942         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6943     }
6944
6945     if (clickType == Release && x == fromX && y == fromY) {
6946         DragPieceEnd(xPix, yPix); dragging = 0;
6947         if(clearFlag) {
6948             // a deferred attempt to click-click move an empty square on top of a piece
6949             boards[currentMove][y][x] = EmptySquare;
6950             ClearHighlights();
6951             DrawPosition(FALSE, boards[currentMove]);
6952             fromX = fromY = -1; clearFlag = 0;
6953             return;
6954         }
6955         if (appData.animateDragging) {
6956             /* Undo animation damage if any */
6957             DrawPosition(FALSE, NULL);
6958         }
6959         if (second) {
6960             /* Second up/down in same square; just abort move */
6961             second = 0;
6962             fromX = fromY = -1;
6963             gatingPiece = EmptySquare;
6964             ClearHighlights();
6965             gotPremove = 0;
6966             ClearPremoveHighlights();
6967         } else {
6968             /* First upclick in same square; start click-click mode */
6969             SetHighlights(x, y, -1, -1);
6970         }
6971         return;
6972     }
6973
6974     clearFlag = 0;
6975
6976     /* we now have a different from- and (possibly off-board) to-square */
6977     /* Completed move */
6978     toX = x;
6979     toY = y;
6980     saveAnimate = appData.animate;
6981     MarkTargetSquares(1);
6982     if (clickType == Press) {
6983         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6984             // must be Edit Position mode with empty-square selected
6985             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
6986             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6987             return;
6988         }
6989         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
6990             ChessSquare piece = boards[currentMove][fromY][fromX];
6991             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
6992             promoSweep = defaultPromoChoice;
6993             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
6994             selectFlag = 0; lastX = xPix; lastY = yPix;
6995             Sweep(0); // Pawn that is going to promote: preview promotion piece
6996             DisplayMessage("", _("Pull pawn backwards to under-promote"));
6997             DrawPosition(FALSE, boards[currentMove]);
6998             return;
6999         }
7000         /* Finish clickclick move */
7001         if (appData.animate || appData.highlightLastMove) {
7002             SetHighlights(fromX, fromY, toX, toY);
7003         } else {
7004             ClearHighlights();
7005         }
7006     } else {
7007         /* Finish drag move */
7008         if (appData.highlightLastMove) {
7009             SetHighlights(fromX, fromY, toX, toY);
7010         } else {
7011             ClearHighlights();
7012         }
7013         DragPieceEnd(xPix, yPix); dragging = 0;
7014         /* Don't animate move and drag both */
7015         appData.animate = FALSE;
7016     }
7017
7018     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7019     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7020         ChessSquare piece = boards[currentMove][fromY][fromX];
7021         if(gameMode == EditPosition && piece != EmptySquare &&
7022            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7023             int n;
7024
7025             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7026                 n = PieceToNumber(piece - (int)BlackPawn);
7027                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7028                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7029                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7030             } else
7031             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7032                 n = PieceToNumber(piece);
7033                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7034                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7035                 boards[currentMove][n][BOARD_WIDTH-2]++;
7036             }
7037             boards[currentMove][fromY][fromX] = EmptySquare;
7038         }
7039         ClearHighlights();
7040         fromX = fromY = -1;
7041         DrawPosition(TRUE, boards[currentMove]);
7042         return;
7043     }
7044
7045     // off-board moves should not be highlighted
7046     if(x < 0 || y < 0) ClearHighlights();
7047
7048     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7049
7050     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7051         SetHighlights(fromX, fromY, toX, toY);
7052         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7053             // [HGM] super: promotion to captured piece selected from holdings
7054             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7055             promotionChoice = TRUE;
7056             // kludge follows to temporarily execute move on display, without promoting yet
7057             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7058             boards[currentMove][toY][toX] = p;
7059             DrawPosition(FALSE, boards[currentMove]);
7060             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7061             boards[currentMove][toY][toX] = q;
7062             DisplayMessage("Click in holdings to choose piece", "");
7063             return;
7064         }
7065         PromotionPopUp();
7066     } else {
7067         int oldMove = currentMove;
7068         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7069         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7070         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7071         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7072            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7073             DrawPosition(TRUE, boards[currentMove]);
7074         fromX = fromY = -1;
7075     }
7076     appData.animate = saveAnimate;
7077     if (appData.animate || appData.animateDragging) {
7078         /* Undo animation damage if needed */
7079         DrawPosition(FALSE, NULL);
7080     }
7081 }
7082
7083 int
7084 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7085 {   // front-end-free part taken out of PieceMenuPopup
7086     int whichMenu; int xSqr, ySqr;
7087
7088     if(seekGraphUp) { // [HGM] seekgraph
7089         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7090         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7091         return -2;
7092     }
7093
7094     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7095          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7096         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7097         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7098         if(action == Press)   {
7099             originalFlip = flipView;
7100             flipView = !flipView; // temporarily flip board to see game from partners perspective
7101             DrawPosition(TRUE, partnerBoard);
7102             DisplayMessage(partnerStatus, "");
7103             partnerUp = TRUE;
7104         } else if(action == Release) {
7105             flipView = originalFlip;
7106             DrawPosition(TRUE, boards[currentMove]);
7107             partnerUp = FALSE;
7108         }
7109         return -2;
7110     }
7111
7112     xSqr = EventToSquare(x, BOARD_WIDTH);
7113     ySqr = EventToSquare(y, BOARD_HEIGHT);
7114     if (action == Release) {
7115         if(pieceSweep != EmptySquare) {
7116             EditPositionMenuEvent(pieceSweep, toX, toY);
7117             pieceSweep = EmptySquare;
7118         } else UnLoadPV(); // [HGM] pv
7119     }
7120     if (action != Press) return -2; // return code to be ignored
7121     switch (gameMode) {
7122       case IcsExamining:
7123         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7124       case EditPosition:
7125         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7126         if (xSqr < 0 || ySqr < 0) return -1;
7127         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7128         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7129         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7130         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7131         NextPiece(0);
7132         return 2; // grab
7133       case IcsObserving:
7134         if(!appData.icsEngineAnalyze) return -1;
7135       case IcsPlayingWhite:
7136       case IcsPlayingBlack:
7137         if(!appData.zippyPlay) goto noZip;
7138       case AnalyzeMode:
7139       case AnalyzeFile:
7140       case MachinePlaysWhite:
7141       case MachinePlaysBlack:
7142       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7143         if (!appData.dropMenu) {
7144           LoadPV(x, y);
7145           return 2; // flag front-end to grab mouse events
7146         }
7147         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7148            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7149       case EditGame:
7150       noZip:
7151         if (xSqr < 0 || ySqr < 0) return -1;
7152         if (!appData.dropMenu || appData.testLegality &&
7153             gameInfo.variant != VariantBughouse &&
7154             gameInfo.variant != VariantCrazyhouse) return -1;
7155         whichMenu = 1; // drop menu
7156         break;
7157       default:
7158         return -1;
7159     }
7160
7161     if (((*fromX = xSqr) < 0) ||
7162         ((*fromY = ySqr) < 0)) {
7163         *fromX = *fromY = -1;
7164         return -1;
7165     }
7166     if (flipView)
7167       *fromX = BOARD_WIDTH - 1 - *fromX;
7168     else
7169       *fromY = BOARD_HEIGHT - 1 - *fromY;
7170
7171     return whichMenu;
7172 }
7173
7174 void
7175 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7176 {
7177 //    char * hint = lastHint;
7178     FrontEndProgramStats stats;
7179
7180     stats.which = cps == &first ? 0 : 1;
7181     stats.depth = cpstats->depth;
7182     stats.nodes = cpstats->nodes;
7183     stats.score = cpstats->score;
7184     stats.time = cpstats->time;
7185     stats.pv = cpstats->movelist;
7186     stats.hint = lastHint;
7187     stats.an_move_index = 0;
7188     stats.an_move_count = 0;
7189
7190     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7191         stats.hint = cpstats->move_name;
7192         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7193         stats.an_move_count = cpstats->nr_moves;
7194     }
7195
7196     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
7197
7198     SetProgramStats( &stats );
7199 }
7200
7201 void
7202 ClearEngineOutputPane (int which)
7203 {
7204     static FrontEndProgramStats dummyStats;
7205     dummyStats.which = which;
7206     dummyStats.pv = "#";
7207     SetProgramStats( &dummyStats );
7208 }
7209
7210 #define MAXPLAYERS 500
7211
7212 char *
7213 TourneyStandings (int display)
7214 {
7215     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7216     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7217     char result, *p, *names[MAXPLAYERS];
7218
7219     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7220         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7221     names[0] = p = strdup(appData.participants);
7222     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7223
7224     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7225
7226     while(result = appData.results[nr]) {
7227         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7228         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7229         wScore = bScore = 0;
7230         switch(result) {
7231           case '+': wScore = 2; break;
7232           case '-': bScore = 2; break;
7233           case '=': wScore = bScore = 1; break;
7234           case ' ':
7235           case '*': return strdup("busy"); // tourney not finished
7236         }
7237         score[w] += wScore;
7238         score[b] += bScore;
7239         games[w]++;
7240         games[b]++;
7241         nr++;
7242     }
7243     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7244     for(w=0; w<nPlayers; w++) {
7245         bScore = -1;
7246         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7247         ranking[w] = b; points[w] = bScore; score[b] = -2;
7248     }
7249     p = malloc(nPlayers*34+1);
7250     for(w=0; w<nPlayers && w<display; w++)
7251         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7252     free(names[0]);
7253     return p;
7254 }
7255
7256 void
7257 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7258 {       // count all piece types
7259         int p, f, r;
7260         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7261         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7262         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7263                 p = board[r][f];
7264                 pCnt[p]++;
7265                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7266                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7267                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7268                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7269                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7270                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7271         }
7272 }
7273
7274 int
7275 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7276 {
7277         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7278         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7279
7280         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7281         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7282         if(myPawns == 2 && nMine == 3) // KPP
7283             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7284         if(myPawns == 1 && nMine == 2) // KP
7285             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7286         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7287             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7288         if(myPawns) return FALSE;
7289         if(pCnt[WhiteRook+side])
7290             return pCnt[BlackRook-side] ||
7291                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7292                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7293                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7294         if(pCnt[WhiteCannon+side]) {
7295             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7296             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7297         }
7298         if(pCnt[WhiteKnight+side])
7299             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7300         return FALSE;
7301 }
7302
7303 int
7304 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7305 {
7306         VariantClass v = gameInfo.variant;
7307
7308         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7309         if(v == VariantShatranj) return TRUE; // always winnable through baring
7310         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7311         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7312
7313         if(v == VariantXiangqi) {
7314                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7315
7316                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7317                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7318                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7319                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7320                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7321                 if(stale) // we have at least one last-rank P plus perhaps C
7322                     return majors // KPKX
7323                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7324                 else // KCA*E*
7325                     return pCnt[WhiteFerz+side] // KCAK
7326                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7327                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7328                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7329
7330         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7331                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7332
7333                 if(nMine == 1) return FALSE; // bare King
7334                 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
7335                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7336                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7337                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7338                 if(pCnt[WhiteKnight+side])
7339                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7340                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7341                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7342                 if(nBishops)
7343                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7344                 if(pCnt[WhiteAlfil+side])
7345                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7346                 if(pCnt[WhiteWazir+side])
7347                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7348         }
7349
7350         return TRUE;
7351 }
7352
7353 int
7354 CompareWithRights (Board b1, Board b2)
7355 {
7356     int rights = 0;
7357     if(!CompareBoards(b1, b2)) return FALSE;
7358     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7359     /* compare castling rights */
7360     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7361            rights++; /* King lost rights, while rook still had them */
7362     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7363         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7364            rights++; /* but at least one rook lost them */
7365     }
7366     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7367            rights++;
7368     if( b1[CASTLING][5] != NoRights ) {
7369         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7370            rights++;
7371     }
7372     return rights == 0;
7373 }
7374
7375 int
7376 Adjudicate (ChessProgramState *cps)
7377 {       // [HGM] some adjudications useful with buggy engines
7378         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7379         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7380         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7381         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7382         int k, count = 0; static int bare = 1;
7383         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7384         Boolean canAdjudicate = !appData.icsActive;
7385
7386         // most tests only when we understand the game, i.e. legality-checking on
7387             if( appData.testLegality )
7388             {   /* [HGM] Some more adjudications for obstinate engines */
7389                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7390                 static int moveCount = 6;
7391                 ChessMove result;
7392                 char *reason = NULL;
7393
7394                 /* Count what is on board. */
7395                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7396
7397                 /* Some material-based adjudications that have to be made before stalemate test */
7398                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7399                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7400                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7401                      if(canAdjudicate && appData.checkMates) {
7402                          if(engineOpponent)
7403                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7404                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7405                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7406                          return 1;
7407                      }
7408                 }
7409
7410                 /* Bare King in Shatranj (loses) or Losers (wins) */
7411                 if( nrW == 1 || nrB == 1) {
7412                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7413                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7414                      if(canAdjudicate && appData.checkMates) {
7415                          if(engineOpponent)
7416                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7417                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7418                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7419                          return 1;
7420                      }
7421                   } else
7422                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7423                   {    /* bare King */
7424                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7425                         if(canAdjudicate && appData.checkMates) {
7426                             /* but only adjudicate if adjudication enabled */
7427                             if(engineOpponent)
7428                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7429                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7430                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7431                             return 1;
7432                         }
7433                   }
7434                 } else bare = 1;
7435
7436
7437             // don't wait for engine to announce game end if we can judge ourselves
7438             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7439               case MT_CHECK:
7440                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7441                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7442                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7443                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7444                             checkCnt++;
7445                         if(checkCnt >= 2) {
7446                             reason = "Xboard adjudication: 3rd check";
7447                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7448                             break;
7449                         }
7450                     }
7451                 }
7452               case MT_NONE:
7453               default:
7454                 break;
7455               case MT_STALEMATE:
7456               case MT_STAINMATE:
7457                 reason = "Xboard adjudication: Stalemate";
7458                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7459                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7460                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7461                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7462                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7463                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7464                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7465                                                                         EP_CHECKMATE : EP_WINS);
7466                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7467                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7468                 }
7469                 break;
7470               case MT_CHECKMATE:
7471                 reason = "Xboard adjudication: Checkmate";
7472                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7473                 break;
7474             }
7475
7476                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7477                     case EP_STALEMATE:
7478                         result = GameIsDrawn; break;
7479                     case EP_CHECKMATE:
7480                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7481                     case EP_WINS:
7482                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7483                     default:
7484                         result = EndOfFile;
7485                 }
7486                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7487                     if(engineOpponent)
7488                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7489                     GameEnds( result, reason, GE_XBOARD );
7490                     return 1;
7491                 }
7492
7493                 /* Next absolutely insufficient mating material. */
7494                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7495                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7496                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7497
7498                      /* always flag draws, for judging claims */
7499                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7500
7501                      if(canAdjudicate && appData.materialDraws) {
7502                          /* but only adjudicate them if adjudication enabled */
7503                          if(engineOpponent) {
7504                            SendToProgram("force\n", engineOpponent); // suppress reply
7505                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7506                          }
7507                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7508                          return 1;
7509                      }
7510                 }
7511
7512                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7513                 if(gameInfo.variant == VariantXiangqi ?
7514                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7515                  : nrW + nrB == 4 &&
7516                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7517                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7518                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7519                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7520                    ) ) {
7521                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7522                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7523                           if(engineOpponent) {
7524                             SendToProgram("force\n", engineOpponent); // suppress reply
7525                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7526                           }
7527                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7528                           return 1;
7529                      }
7530                 } else moveCount = 6;
7531             }
7532         if (appData.debugMode) { int i;
7533             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7534                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7535                     appData.drawRepeats);
7536             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7537               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7538
7539         }
7540
7541         // Repetition draws and 50-move rule can be applied independently of legality testing
7542
7543                 /* Check for rep-draws */
7544                 count = 0;
7545                 for(k = forwardMostMove-2;
7546                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7547                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7548                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7549                     k-=2)
7550                 {   int rights=0;
7551                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7552                         /* compare castling rights */
7553                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7554                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7555                                 rights++; /* King lost rights, while rook still had them */
7556                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7557                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7558                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7559                                    rights++; /* but at least one rook lost them */
7560                         }
7561                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7562                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7563                                 rights++;
7564                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7565                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7566                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7567                                    rights++;
7568                         }
7569                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7570                             && appData.drawRepeats > 1) {
7571                              /* adjudicate after user-specified nr of repeats */
7572                              int result = GameIsDrawn;
7573                              char *details = "XBoard adjudication: repetition draw";
7574                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7575                                 // [HGM] xiangqi: check for forbidden perpetuals
7576                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7577                                 for(m=forwardMostMove; m>k; m-=2) {
7578                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7579                                         ourPerpetual = 0; // the current mover did not always check
7580                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7581                                         hisPerpetual = 0; // the opponent did not always check
7582                                 }
7583                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7584                                                                         ourPerpetual, hisPerpetual);
7585                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7586                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7587                                     details = "Xboard adjudication: perpetual checking";
7588                                 } else
7589                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7590                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7591                                 } else
7592                                 // Now check for perpetual chases
7593                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7594                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7595                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7596                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7597                                         static char resdet[MSG_SIZ];
7598                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7599                                         details = resdet;
7600                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7601                                     } else
7602                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7603                                         break; // Abort repetition-checking loop.
7604                                 }
7605                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7606                              }
7607                              if(engineOpponent) {
7608                                SendToProgram("force\n", engineOpponent); // suppress reply
7609                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7610                              }
7611                              GameEnds( result, details, GE_XBOARD );
7612                              return 1;
7613                         }
7614                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7615                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7616                     }
7617                 }
7618
7619                 /* Now we test for 50-move draws. Determine ply count */
7620                 count = forwardMostMove;
7621                 /* look for last irreversble move */
7622                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7623                     count--;
7624                 /* if we hit starting position, add initial plies */
7625                 if( count == backwardMostMove )
7626                     count -= initialRulePlies;
7627                 count = forwardMostMove - count;
7628                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7629                         // adjust reversible move counter for checks in Xiangqi
7630                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7631                         if(i < backwardMostMove) i = backwardMostMove;
7632                         while(i <= forwardMostMove) {
7633                                 lastCheck = inCheck; // check evasion does not count
7634                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7635                                 if(inCheck || lastCheck) count--; // check does not count
7636                                 i++;
7637                         }
7638                 }
7639                 if( count >= 100)
7640                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7641                          /* this is used to judge if draw claims are legal */
7642                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7643                          if(engineOpponent) {
7644                            SendToProgram("force\n", engineOpponent); // suppress reply
7645                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7646                          }
7647                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7648                          return 1;
7649                 }
7650
7651                 /* if draw offer is pending, treat it as a draw claim
7652                  * when draw condition present, to allow engines a way to
7653                  * claim draws before making their move to avoid a race
7654                  * condition occurring after their move
7655                  */
7656                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7657                          char *p = NULL;
7658                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7659                              p = "Draw claim: 50-move rule";
7660                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7661                              p = "Draw claim: 3-fold repetition";
7662                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7663                              p = "Draw claim: insufficient mating material";
7664                          if( p != NULL && canAdjudicate) {
7665                              if(engineOpponent) {
7666                                SendToProgram("force\n", engineOpponent); // suppress reply
7667                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7668                              }
7669                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7670                              return 1;
7671                          }
7672                 }
7673
7674                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7675                     if(engineOpponent) {
7676                       SendToProgram("force\n", engineOpponent); // suppress reply
7677                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7678                     }
7679                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7680                     return 1;
7681                 }
7682         return 0;
7683 }
7684
7685 char *
7686 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7687 {   // [HGM] book: this routine intercepts moves to simulate book replies
7688     char *bookHit = NULL;
7689
7690     //first determine if the incoming move brings opponent into his book
7691     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7692         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7693     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7694     if(bookHit != NULL && !cps->bookSuspend) {
7695         // make sure opponent is not going to reply after receiving move to book position
7696         SendToProgram("force\n", cps);
7697         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7698     }
7699     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7700     // now arrange restart after book miss
7701     if(bookHit) {
7702         // after a book hit we never send 'go', and the code after the call to this routine
7703         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7704         char buf[MSG_SIZ], *move = bookHit;
7705         if(cps->useSAN) {
7706             int fromX, fromY, toX, toY;
7707             char promoChar;
7708             ChessMove moveType;
7709             move = buf + 30;
7710             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7711                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7712                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7713                                     PosFlags(forwardMostMove),
7714                                     fromY, fromX, toY, toX, promoChar, move);
7715             } else {
7716                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7717                 bookHit = NULL;
7718             }
7719         }
7720         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7721         SendToProgram(buf, cps);
7722         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7723     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7724         SendToProgram("go\n", cps);
7725         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7726     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7727         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7728             SendToProgram("go\n", cps);
7729         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7730     }
7731     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7732 }
7733
7734 char *savedMessage;
7735 ChessProgramState *savedState;
7736 void
7737 DeferredBookMove (void)
7738 {
7739         if(savedState->lastPing != savedState->lastPong)
7740                     ScheduleDelayedEvent(DeferredBookMove, 10);
7741         else
7742         HandleMachineMove(savedMessage, savedState);
7743 }
7744
7745 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7746
7747 void
7748 HandleMachineMove (char *message, ChessProgramState *cps)
7749 {
7750     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7751     char realname[MSG_SIZ];
7752     int fromX, fromY, toX, toY;
7753     ChessMove moveType;
7754     char promoChar;
7755     char *p, *pv=buf1;
7756     int machineWhite;
7757     char *bookHit;
7758
7759     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7760         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7761         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7762             DisplayError(_("Invalid pairing from pairing engine"), 0);
7763             return;
7764         }
7765         pairingReceived = 1;
7766         NextMatchGame();
7767         return; // Skim the pairing messages here.
7768     }
7769
7770     cps->userError = 0;
7771
7772 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7773     /*
7774      * Kludge to ignore BEL characters
7775      */
7776     while (*message == '\007') message++;
7777
7778     /*
7779      * [HGM] engine debug message: ignore lines starting with '#' character
7780      */
7781     if(cps->debug && *message == '#') return;
7782
7783     /*
7784      * Look for book output
7785      */
7786     if (cps == &first && bookRequested) {
7787         if (message[0] == '\t' || message[0] == ' ') {
7788             /* Part of the book output is here; append it */
7789             strcat(bookOutput, message);
7790             strcat(bookOutput, "  \n");
7791             return;
7792         } else if (bookOutput[0] != NULLCHAR) {
7793             /* All of book output has arrived; display it */
7794             char *p = bookOutput;
7795             while (*p != NULLCHAR) {
7796                 if (*p == '\t') *p = ' ';
7797                 p++;
7798             }
7799             DisplayInformation(bookOutput);
7800             bookRequested = FALSE;
7801             /* Fall through to parse the current output */
7802         }
7803     }
7804
7805     /*
7806      * Look for machine move.
7807      */
7808     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7809         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7810     {
7811         /* This method is only useful on engines that support ping */
7812         if (cps->lastPing != cps->lastPong) {
7813           if (gameMode == BeginningOfGame) {
7814             /* Extra move from before last new; ignore */
7815             if (appData.debugMode) {
7816                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7817             }
7818           } else {
7819             if (appData.debugMode) {
7820                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7821                         cps->which, gameMode);
7822             }
7823
7824             SendToProgram("undo\n", cps);
7825           }
7826           return;
7827         }
7828
7829         switch (gameMode) {
7830           case BeginningOfGame:
7831             /* Extra move from before last reset; ignore */
7832             if (appData.debugMode) {
7833                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7834             }
7835             return;
7836
7837           case EndOfGame:
7838           case IcsIdle:
7839           default:
7840             /* Extra move after we tried to stop.  The mode test is
7841                not a reliable way of detecting this problem, but it's
7842                the best we can do on engines that don't support ping.
7843             */
7844             if (appData.debugMode) {
7845                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7846                         cps->which, gameMode);
7847             }
7848             SendToProgram("undo\n", cps);
7849             return;
7850
7851           case MachinePlaysWhite:
7852           case IcsPlayingWhite:
7853             machineWhite = TRUE;
7854             break;
7855
7856           case MachinePlaysBlack:
7857           case IcsPlayingBlack:
7858             machineWhite = FALSE;
7859             break;
7860
7861           case TwoMachinesPlay:
7862             machineWhite = (cps->twoMachinesColor[0] == 'w');
7863             break;
7864         }
7865         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7866             if (appData.debugMode) {
7867                 fprintf(debugFP,
7868                         "Ignoring move out of turn by %s, gameMode %d"
7869                         ", forwardMost %d\n",
7870                         cps->which, gameMode, forwardMostMove);
7871             }
7872             return;
7873         }
7874
7875     if (appData.debugMode) { int f = forwardMostMove;
7876         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7877                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7878                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7879     }
7880         if(cps->alphaRank) AlphaRank(machineMove, 4);
7881         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7882                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7883             /* Machine move could not be parsed; ignore it. */
7884           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7885                     machineMove, _(cps->which));
7886             DisplayError(buf1, 0);
7887             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7888                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7889             if (gameMode == TwoMachinesPlay) {
7890               GameEnds(machineWhite ? BlackWins : WhiteWins,
7891                        buf1, GE_XBOARD);
7892             }
7893             return;
7894         }
7895
7896         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7897         /* So we have to redo legality test with true e.p. status here,  */
7898         /* to make sure an illegal e.p. capture does not slip through,   */
7899         /* to cause a forfeit on a justified illegal-move complaint      */
7900         /* of the opponent.                                              */
7901         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7902            ChessMove moveType;
7903            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7904                              fromY, fromX, toY, toX, promoChar);
7905             if (appData.debugMode) {
7906                 int i;
7907                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7908                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7909                 fprintf(debugFP, "castling rights\n");
7910             }
7911             if(moveType == IllegalMove) {
7912               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7913                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7914                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7915                            buf1, GE_XBOARD);
7916                 return;
7917            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7918            /* [HGM] Kludge to handle engines that send FRC-style castling
7919               when they shouldn't (like TSCP-Gothic) */
7920            switch(moveType) {
7921              case WhiteASideCastleFR:
7922              case BlackASideCastleFR:
7923                toX+=2;
7924                currentMoveString[2]++;
7925                break;
7926              case WhiteHSideCastleFR:
7927              case BlackHSideCastleFR:
7928                toX--;
7929                currentMoveString[2]--;
7930                break;
7931              default: ; // nothing to do, but suppresses warning of pedantic compilers
7932            }
7933         }
7934         hintRequested = FALSE;
7935         lastHint[0] = NULLCHAR;
7936         bookRequested = FALSE;
7937         /* Program may be pondering now */
7938         cps->maybeThinking = TRUE;
7939         if (cps->sendTime == 2) cps->sendTime = 1;
7940         if (cps->offeredDraw) cps->offeredDraw--;
7941
7942         /* [AS] Save move info*/
7943         pvInfoList[ forwardMostMove ].score = programStats.score;
7944         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7945         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7946
7947         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7948
7949         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7950         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7951             int count = 0;
7952
7953             while( count < adjudicateLossPlies ) {
7954                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7955
7956                 if( count & 1 ) {
7957                     score = -score; /* Flip score for winning side */
7958                 }
7959
7960                 if( score > adjudicateLossThreshold ) {
7961                     break;
7962                 }
7963
7964                 count++;
7965             }
7966
7967             if( count >= adjudicateLossPlies ) {
7968                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7969
7970                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7971                     "Xboard adjudication",
7972                     GE_XBOARD );
7973
7974                 return;
7975             }
7976         }
7977
7978         if(Adjudicate(cps)) {
7979             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7980             return; // [HGM] adjudicate: for all automatic game ends
7981         }
7982
7983 #if ZIPPY
7984         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7985             first.initDone) {
7986           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7987                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7988                 SendToICS("draw ");
7989                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7990           }
7991           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7992           ics_user_moved = 1;
7993           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7994                 char buf[3*MSG_SIZ];
7995
7996                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7997                         programStats.score / 100.,
7998                         programStats.depth,
7999                         programStats.time / 100.,
8000                         (unsigned int)programStats.nodes,
8001                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8002                         programStats.movelist);
8003                 SendToICS(buf);
8004 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8005           }
8006         }
8007 #endif
8008
8009         /* [AS] Clear stats for next move */
8010         ClearProgramStats();
8011         thinkOutput[0] = NULLCHAR;
8012         hiddenThinkOutputState = 0;
8013
8014         bookHit = NULL;
8015         if (gameMode == TwoMachinesPlay) {
8016             /* [HGM] relaying draw offers moved to after reception of move */
8017             /* and interpreting offer as claim if it brings draw condition */
8018             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8019                 SendToProgram("draw\n", cps->other);
8020             }
8021             if (cps->other->sendTime) {
8022                 SendTimeRemaining(cps->other,
8023                                   cps->other->twoMachinesColor[0] == 'w');
8024             }
8025             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8026             if (firstMove && !bookHit) {
8027                 firstMove = FALSE;
8028                 if (cps->other->useColors) {
8029                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8030                 }
8031                 SendToProgram("go\n", cps->other);
8032             }
8033             cps->other->maybeThinking = TRUE;
8034         }
8035
8036         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8037
8038         if (!pausing && appData.ringBellAfterMoves) {
8039             RingBell();
8040         }
8041
8042         /*
8043          * Reenable menu items that were disabled while
8044          * machine was thinking
8045          */
8046         if (gameMode != TwoMachinesPlay)
8047             SetUserThinkingEnables();
8048
8049         // [HGM] book: after book hit opponent has received move and is now in force mode
8050         // force the book reply into it, and then fake that it outputted this move by jumping
8051         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8052         if(bookHit) {
8053                 static char bookMove[MSG_SIZ]; // a bit generous?
8054
8055                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8056                 strcat(bookMove, bookHit);
8057                 message = bookMove;
8058                 cps = cps->other;
8059                 programStats.nodes = programStats.depth = programStats.time =
8060                 programStats.score = programStats.got_only_move = 0;
8061                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8062
8063                 if(cps->lastPing != cps->lastPong) {
8064                     savedMessage = message; // args for deferred call
8065                     savedState = cps;
8066                     ScheduleDelayedEvent(DeferredBookMove, 10);
8067                     return;
8068                 }
8069                 goto FakeBookMove;
8070         }
8071
8072         return;
8073     }
8074
8075     /* Set special modes for chess engines.  Later something general
8076      *  could be added here; for now there is just one kludge feature,
8077      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8078      *  when "xboard" is given as an interactive command.
8079      */
8080     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8081         cps->useSigint = FALSE;
8082         cps->useSigterm = FALSE;
8083     }
8084     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8085       ParseFeatures(message+8, cps);
8086       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8087     }
8088
8089     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8090                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8091       int dummy, s=6; char buf[MSG_SIZ];
8092       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8093       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8094       if(startedFromSetupPosition) return;
8095       ParseFEN(boards[0], &dummy, message+s);
8096       DrawPosition(TRUE, boards[0]);
8097       startedFromSetupPosition = TRUE;
8098       return;
8099     }
8100     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8101      * want this, I was asked to put it in, and obliged.
8102      */
8103     if (!strncmp(message, "setboard ", 9)) {
8104         Board initial_position;
8105
8106         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8107
8108         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8109             DisplayError(_("Bad FEN received from engine"), 0);
8110             return ;
8111         } else {
8112            Reset(TRUE, FALSE);
8113            CopyBoard(boards[0], initial_position);
8114            initialRulePlies = FENrulePlies;
8115            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8116            else gameMode = MachinePlaysBlack;
8117            DrawPosition(FALSE, boards[currentMove]);
8118         }
8119         return;
8120     }
8121
8122     /*
8123      * Look for communication commands
8124      */
8125     if (!strncmp(message, "telluser ", 9)) {
8126         if(message[9] == '\\' && message[10] == '\\')
8127             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8128         PlayTellSound();
8129         DisplayNote(message + 9);
8130         return;
8131     }
8132     if (!strncmp(message, "tellusererror ", 14)) {
8133         cps->userError = 1;
8134         if(message[14] == '\\' && message[15] == '\\')
8135             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8136         PlayTellSound();
8137         DisplayError(message + 14, 0);
8138         return;
8139     }
8140     if (!strncmp(message, "tellopponent ", 13)) {
8141       if (appData.icsActive) {
8142         if (loggedOn) {
8143           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8144           SendToICS(buf1);
8145         }
8146       } else {
8147         DisplayNote(message + 13);
8148       }
8149       return;
8150     }
8151     if (!strncmp(message, "tellothers ", 11)) {
8152       if (appData.icsActive) {
8153         if (loggedOn) {
8154           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8155           SendToICS(buf1);
8156         }
8157       }
8158       return;
8159     }
8160     if (!strncmp(message, "tellall ", 8)) {
8161       if (appData.icsActive) {
8162         if (loggedOn) {
8163           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8164           SendToICS(buf1);
8165         }
8166       } else {
8167         DisplayNote(message + 8);
8168       }
8169       return;
8170     }
8171     if (strncmp(message, "warning", 7) == 0) {
8172         /* Undocumented feature, use tellusererror in new code */
8173         DisplayError(message, 0);
8174         return;
8175     }
8176     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8177         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8178         strcat(realname, " query");
8179         AskQuestion(realname, buf2, buf1, cps->pr);
8180         return;
8181     }
8182     /* Commands from the engine directly to ICS.  We don't allow these to be
8183      *  sent until we are logged on. Crafty kibitzes have been known to
8184      *  interfere with the login process.
8185      */
8186     if (loggedOn) {
8187         if (!strncmp(message, "tellics ", 8)) {
8188             SendToICS(message + 8);
8189             SendToICS("\n");
8190             return;
8191         }
8192         if (!strncmp(message, "tellicsnoalias ", 15)) {
8193             SendToICS(ics_prefix);
8194             SendToICS(message + 15);
8195             SendToICS("\n");
8196             return;
8197         }
8198         /* The following are for backward compatibility only */
8199         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8200             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8201             SendToICS(ics_prefix);
8202             SendToICS(message);
8203             SendToICS("\n");
8204             return;
8205         }
8206     }
8207     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8208         return;
8209     }
8210     /*
8211      * If the move is illegal, cancel it and redraw the board.
8212      * Also deal with other error cases.  Matching is rather loose
8213      * here to accommodate engines written before the spec.
8214      */
8215     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8216         strncmp(message, "Error", 5) == 0) {
8217         if (StrStr(message, "name") ||
8218             StrStr(message, "rating") || StrStr(message, "?") ||
8219             StrStr(message, "result") || StrStr(message, "board") ||
8220             StrStr(message, "bk") || StrStr(message, "computer") ||
8221             StrStr(message, "variant") || StrStr(message, "hint") ||
8222             StrStr(message, "random") || StrStr(message, "depth") ||
8223             StrStr(message, "accepted")) {
8224             return;
8225         }
8226         if (StrStr(message, "protover")) {
8227           /* Program is responding to input, so it's apparently done
8228              initializing, and this error message indicates it is
8229              protocol version 1.  So we don't need to wait any longer
8230              for it to initialize and send feature commands. */
8231           FeatureDone(cps, 1);
8232           cps->protocolVersion = 1;
8233           return;
8234         }
8235         cps->maybeThinking = FALSE;
8236
8237         if (StrStr(message, "draw")) {
8238             /* Program doesn't have "draw" command */
8239             cps->sendDrawOffers = 0;
8240             return;
8241         }
8242         if (cps->sendTime != 1 &&
8243             (StrStr(message, "time") || StrStr(message, "otim"))) {
8244           /* Program apparently doesn't have "time" or "otim" command */
8245           cps->sendTime = 0;
8246           return;
8247         }
8248         if (StrStr(message, "analyze")) {
8249             cps->analysisSupport = FALSE;
8250             cps->analyzing = FALSE;
8251 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8252             EditGameEvent(); // [HGM] try to preserve loaded game
8253             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8254             DisplayError(buf2, 0);
8255             return;
8256         }
8257         if (StrStr(message, "(no matching move)st")) {
8258           /* Special kludge for GNU Chess 4 only */
8259           cps->stKludge = TRUE;
8260           SendTimeControl(cps, movesPerSession, timeControl,
8261                           timeIncrement, appData.searchDepth,
8262                           searchTime);
8263           return;
8264         }
8265         if (StrStr(message, "(no matching move)sd")) {
8266           /* Special kludge for GNU Chess 4 only */
8267           cps->sdKludge = TRUE;
8268           SendTimeControl(cps, movesPerSession, timeControl,
8269                           timeIncrement, appData.searchDepth,
8270                           searchTime);
8271           return;
8272         }
8273         if (!StrStr(message, "llegal")) {
8274             return;
8275         }
8276         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8277             gameMode == IcsIdle) return;
8278         if (forwardMostMove <= backwardMostMove) return;
8279         if (pausing) PauseEvent();
8280       if(appData.forceIllegal) {
8281             // [HGM] illegal: machine refused move; force position after move into it
8282           SendToProgram("force\n", cps);
8283           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8284                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8285                 // when black is to move, while there might be nothing on a2 or black
8286                 // might already have the move. So send the board as if white has the move.
8287                 // But first we must change the stm of the engine, as it refused the last move
8288                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8289                 if(WhiteOnMove(forwardMostMove)) {
8290                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8291                     SendBoard(cps, forwardMostMove); // kludgeless board
8292                 } else {
8293                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8294                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8295                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8296                 }
8297           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8298             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8299                  gameMode == TwoMachinesPlay)
8300               SendToProgram("go\n", cps);
8301             return;
8302       } else
8303         if (gameMode == PlayFromGameFile) {
8304             /* Stop reading this game file */
8305             gameMode = EditGame;
8306             ModeHighlight();
8307         }
8308         /* [HGM] illegal-move claim should forfeit game when Xboard */
8309         /* only passes fully legal moves                            */
8310         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8311             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8312                                 "False illegal-move claim", GE_XBOARD );
8313             return; // do not take back move we tested as valid
8314         }
8315         currentMove = forwardMostMove-1;
8316         DisplayMove(currentMove-1); /* before DisplayMoveError */
8317         SwitchClocks(forwardMostMove-1); // [HGM] race
8318         DisplayBothClocks();
8319         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8320                 parseList[currentMove], _(cps->which));
8321         DisplayMoveError(buf1);
8322         DrawPosition(FALSE, boards[currentMove]);
8323
8324         SetUserThinkingEnables();
8325         return;
8326     }
8327     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8328         /* Program has a broken "time" command that
8329            outputs a string not ending in newline.
8330            Don't use it. */
8331         cps->sendTime = 0;
8332     }
8333
8334     /*
8335      * If chess program startup fails, exit with an error message.
8336      * Attempts to recover here are futile.
8337      */
8338     if ((StrStr(message, "unknown host") != NULL)
8339         || (StrStr(message, "No remote directory") != NULL)
8340         || (StrStr(message, "not found") != NULL)
8341         || (StrStr(message, "No such file") != NULL)
8342         || (StrStr(message, "can't alloc") != NULL)
8343         || (StrStr(message, "Permission denied") != NULL)) {
8344
8345         cps->maybeThinking = FALSE;
8346         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8347                 _(cps->which), cps->program, cps->host, message);
8348         RemoveInputSource(cps->isr);
8349         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8350             if(cps == &first) appData.noChessProgram = TRUE;
8351             DisplayError(buf1, 0);
8352         }
8353         return;
8354     }
8355
8356     /*
8357      * Look for hint output
8358      */
8359     if (sscanf(message, "Hint: %s", buf1) == 1) {
8360         if (cps == &first && hintRequested) {
8361             hintRequested = FALSE;
8362             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8363                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8364                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8365                                     PosFlags(forwardMostMove),
8366                                     fromY, fromX, toY, toX, promoChar, buf1);
8367                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8368                 DisplayInformation(buf2);
8369             } else {
8370                 /* Hint move could not be parsed!? */
8371               snprintf(buf2, sizeof(buf2),
8372                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8373                         buf1, _(cps->which));
8374                 DisplayError(buf2, 0);
8375             }
8376         } else {
8377           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8378         }
8379         return;
8380     }
8381
8382     /*
8383      * Ignore other messages if game is not in progress
8384      */
8385     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8386         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8387
8388     /*
8389      * look for win, lose, draw, or draw offer
8390      */
8391     if (strncmp(message, "1-0", 3) == 0) {
8392         char *p, *q, *r = "";
8393         p = strchr(message, '{');
8394         if (p) {
8395             q = strchr(p, '}');
8396             if (q) {
8397                 *q = NULLCHAR;
8398                 r = p + 1;
8399             }
8400         }
8401         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8402         return;
8403     } else if (strncmp(message, "0-1", 3) == 0) {
8404         char *p, *q, *r = "";
8405         p = strchr(message, '{');
8406         if (p) {
8407             q = strchr(p, '}');
8408             if (q) {
8409                 *q = NULLCHAR;
8410                 r = p + 1;
8411             }
8412         }
8413         /* Kludge for Arasan 4.1 bug */
8414         if (strcmp(r, "Black resigns") == 0) {
8415             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8416             return;
8417         }
8418         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8419         return;
8420     } else if (strncmp(message, "1/2", 3) == 0) {
8421         char *p, *q, *r = "";
8422         p = strchr(message, '{');
8423         if (p) {
8424             q = strchr(p, '}');
8425             if (q) {
8426                 *q = NULLCHAR;
8427                 r = p + 1;
8428             }
8429         }
8430
8431         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8432         return;
8433
8434     } else if (strncmp(message, "White resign", 12) == 0) {
8435         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8436         return;
8437     } else if (strncmp(message, "Black resign", 12) == 0) {
8438         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8439         return;
8440     } else if (strncmp(message, "White matches", 13) == 0 ||
8441                strncmp(message, "Black matches", 13) == 0   ) {
8442         /* [HGM] ignore GNUShogi noises */
8443         return;
8444     } else if (strncmp(message, "White", 5) == 0 &&
8445                message[5] != '(' &&
8446                StrStr(message, "Black") == NULL) {
8447         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8448         return;
8449     } else if (strncmp(message, "Black", 5) == 0 &&
8450                message[5] != '(') {
8451         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8452         return;
8453     } else if (strcmp(message, "resign") == 0 ||
8454                strcmp(message, "computer resigns") == 0) {
8455         switch (gameMode) {
8456           case MachinePlaysBlack:
8457           case IcsPlayingBlack:
8458             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8459             break;
8460           case MachinePlaysWhite:
8461           case IcsPlayingWhite:
8462             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8463             break;
8464           case TwoMachinesPlay:
8465             if (cps->twoMachinesColor[0] == 'w')
8466               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8467             else
8468               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8469             break;
8470           default:
8471             /* can't happen */
8472             break;
8473         }
8474         return;
8475     } else if (strncmp(message, "opponent mates", 14) == 0) {
8476         switch (gameMode) {
8477           case MachinePlaysBlack:
8478           case IcsPlayingBlack:
8479             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8480             break;
8481           case MachinePlaysWhite:
8482           case IcsPlayingWhite:
8483             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8484             break;
8485           case TwoMachinesPlay:
8486             if (cps->twoMachinesColor[0] == 'w')
8487               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8488             else
8489               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8490             break;
8491           default:
8492             /* can't happen */
8493             break;
8494         }
8495         return;
8496     } else if (strncmp(message, "computer mates", 14) == 0) {
8497         switch (gameMode) {
8498           case MachinePlaysBlack:
8499           case IcsPlayingBlack:
8500             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8501             break;
8502           case MachinePlaysWhite:
8503           case IcsPlayingWhite:
8504             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8505             break;
8506           case TwoMachinesPlay:
8507             if (cps->twoMachinesColor[0] == 'w')
8508               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8509             else
8510               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8511             break;
8512           default:
8513             /* can't happen */
8514             break;
8515         }
8516         return;
8517     } else if (strncmp(message, "checkmate", 9) == 0) {
8518         if (WhiteOnMove(forwardMostMove)) {
8519             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8520         } else {
8521             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8522         }
8523         return;
8524     } else if (strstr(message, "Draw") != NULL ||
8525                strstr(message, "game is a draw") != NULL) {
8526         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8527         return;
8528     } else if (strstr(message, "offer") != NULL &&
8529                strstr(message, "draw") != NULL) {
8530 #if ZIPPY
8531         if (appData.zippyPlay && first.initDone) {
8532             /* Relay offer to ICS */
8533             SendToICS(ics_prefix);
8534             SendToICS("draw\n");
8535         }
8536 #endif
8537         cps->offeredDraw = 2; /* valid until this engine moves twice */
8538         if (gameMode == TwoMachinesPlay) {
8539             if (cps->other->offeredDraw) {
8540                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8541             /* [HGM] in two-machine mode we delay relaying draw offer      */
8542             /* until after we also have move, to see if it is really claim */
8543             }
8544         } else if (gameMode == MachinePlaysWhite ||
8545                    gameMode == MachinePlaysBlack) {
8546           if (userOfferedDraw) {
8547             DisplayInformation(_("Machine accepts your draw offer"));
8548             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8549           } else {
8550             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8551           }
8552         }
8553     }
8554
8555
8556     /*
8557      * Look for thinking output
8558      */
8559     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8560           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8561                                 ) {
8562         int plylev, mvleft, mvtot, curscore, time;
8563         char mvname[MOVE_LEN];
8564         u64 nodes; // [DM]
8565         char plyext;
8566         int ignore = FALSE;
8567         int prefixHint = FALSE;
8568         mvname[0] = NULLCHAR;
8569
8570         switch (gameMode) {
8571           case MachinePlaysBlack:
8572           case IcsPlayingBlack:
8573             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8574             break;
8575           case MachinePlaysWhite:
8576           case IcsPlayingWhite:
8577             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8578             break;
8579           case AnalyzeMode:
8580           case AnalyzeFile:
8581             break;
8582           case IcsObserving: /* [DM] icsEngineAnalyze */
8583             if (!appData.icsEngineAnalyze) ignore = TRUE;
8584             break;
8585           case TwoMachinesPlay:
8586             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8587                 ignore = TRUE;
8588             }
8589             break;
8590           default:
8591             ignore = TRUE;
8592             break;
8593         }
8594
8595         if (!ignore) {
8596             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8597             buf1[0] = NULLCHAR;
8598             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8599                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8600
8601                 if (plyext != ' ' && plyext != '\t') {
8602                     time *= 100;
8603                 }
8604
8605                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8606                 if( cps->scoreIsAbsolute &&
8607                     ( gameMode == MachinePlaysBlack ||
8608                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8609                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8610                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8611                      !WhiteOnMove(currentMove)
8612                     ) )
8613                 {
8614                     curscore = -curscore;
8615                 }
8616
8617                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8618
8619                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8620                         char buf[MSG_SIZ];
8621                         FILE *f;
8622                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8623                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8624                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8625                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8626                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8627                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8628                                 fclose(f);
8629                         } else DisplayError(_("failed writing PV"), 0);
8630                 }
8631
8632                 tempStats.depth = plylev;
8633                 tempStats.nodes = nodes;
8634                 tempStats.time = time;
8635                 tempStats.score = curscore;
8636                 tempStats.got_only_move = 0;
8637
8638                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8639                         int ticklen;
8640
8641                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8642                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8643                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8644                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8645                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8646                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8647                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8648                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8649                 }
8650
8651                 /* Buffer overflow protection */
8652                 if (pv[0] != NULLCHAR) {
8653                     if (strlen(pv) >= sizeof(tempStats.movelist)
8654                         && appData.debugMode) {
8655                         fprintf(debugFP,
8656                                 "PV is too long; using the first %u bytes.\n",
8657                                 (unsigned) sizeof(tempStats.movelist) - 1);
8658                     }
8659
8660                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8661                 } else {
8662                     sprintf(tempStats.movelist, " no PV\n");
8663                 }
8664
8665                 if (tempStats.seen_stat) {
8666                     tempStats.ok_to_send = 1;
8667                 }
8668
8669                 if (strchr(tempStats.movelist, '(') != NULL) {
8670                     tempStats.line_is_book = 1;
8671                     tempStats.nr_moves = 0;
8672                     tempStats.moves_left = 0;
8673                 } else {
8674                     tempStats.line_is_book = 0;
8675                 }
8676
8677                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8678                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8679
8680                 SendProgramStatsToFrontend( cps, &tempStats );
8681
8682                 /*
8683                     [AS] Protect the thinkOutput buffer from overflow... this
8684                     is only useful if buf1 hasn't overflowed first!
8685                 */
8686                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8687                          plylev,
8688                          (gameMode == TwoMachinesPlay ?
8689                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8690                          ((double) curscore) / 100.0,
8691                          prefixHint ? lastHint : "",
8692                          prefixHint ? " " : "" );
8693
8694                 if( buf1[0] != NULLCHAR ) {
8695                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8696
8697                     if( strlen(pv) > max_len ) {
8698                         if( appData.debugMode) {
8699                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8700                         }
8701                         pv[max_len+1] = '\0';
8702                     }
8703
8704                     strcat( thinkOutput, pv);
8705                 }
8706
8707                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8708                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8709                     DisplayMove(currentMove - 1);
8710                 }
8711                 return;
8712
8713             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8714                 /* crafty (9.25+) says "(only move) <move>"
8715                  * if there is only 1 legal move
8716                  */
8717                 sscanf(p, "(only move) %s", buf1);
8718                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8719                 sprintf(programStats.movelist, "%s (only move)", buf1);
8720                 programStats.depth = 1;
8721                 programStats.nr_moves = 1;
8722                 programStats.moves_left = 1;
8723                 programStats.nodes = 1;
8724                 programStats.time = 1;
8725                 programStats.got_only_move = 1;
8726
8727                 /* Not really, but we also use this member to
8728                    mean "line isn't going to change" (Crafty
8729                    isn't searching, so stats won't change) */
8730                 programStats.line_is_book = 1;
8731
8732                 SendProgramStatsToFrontend( cps, &programStats );
8733
8734                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8735                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8736                     DisplayMove(currentMove - 1);
8737                 }
8738                 return;
8739             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8740                               &time, &nodes, &plylev, &mvleft,
8741                               &mvtot, mvname) >= 5) {
8742                 /* The stat01: line is from Crafty (9.29+) in response
8743                    to the "." command */
8744                 programStats.seen_stat = 1;
8745                 cps->maybeThinking = TRUE;
8746
8747                 if (programStats.got_only_move || !appData.periodicUpdates)
8748                   return;
8749
8750                 programStats.depth = plylev;
8751                 programStats.time = time;
8752                 programStats.nodes = nodes;
8753                 programStats.moves_left = mvleft;
8754                 programStats.nr_moves = mvtot;
8755                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8756                 programStats.ok_to_send = 1;
8757                 programStats.movelist[0] = '\0';
8758
8759                 SendProgramStatsToFrontend( cps, &programStats );
8760
8761                 return;
8762
8763             } else if (strncmp(message,"++",2) == 0) {
8764                 /* Crafty 9.29+ outputs this */
8765                 programStats.got_fail = 2;
8766                 return;
8767
8768             } else if (strncmp(message,"--",2) == 0) {
8769                 /* Crafty 9.29+ outputs this */
8770                 programStats.got_fail = 1;
8771                 return;
8772
8773             } else if (thinkOutput[0] != NULLCHAR &&
8774                        strncmp(message, "    ", 4) == 0) {
8775                 unsigned message_len;
8776
8777                 p = message;
8778                 while (*p && *p == ' ') p++;
8779
8780                 message_len = strlen( p );
8781
8782                 /* [AS] Avoid buffer overflow */
8783                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8784                     strcat(thinkOutput, " ");
8785                     strcat(thinkOutput, p);
8786                 }
8787
8788                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8789                     strcat(programStats.movelist, " ");
8790                     strcat(programStats.movelist, p);
8791                 }
8792
8793                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8794                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8795                     DisplayMove(currentMove - 1);
8796                 }
8797                 return;
8798             }
8799         }
8800         else {
8801             buf1[0] = NULLCHAR;
8802
8803             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8804                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8805             {
8806                 ChessProgramStats cpstats;
8807
8808                 if (plyext != ' ' && plyext != '\t') {
8809                     time *= 100;
8810                 }
8811
8812                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8813                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8814                     curscore = -curscore;
8815                 }
8816
8817                 cpstats.depth = plylev;
8818                 cpstats.nodes = nodes;
8819                 cpstats.time = time;
8820                 cpstats.score = curscore;
8821                 cpstats.got_only_move = 0;
8822                 cpstats.movelist[0] = '\0';
8823
8824                 if (buf1[0] != NULLCHAR) {
8825                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8826                 }
8827
8828                 cpstats.ok_to_send = 0;
8829                 cpstats.line_is_book = 0;
8830                 cpstats.nr_moves = 0;
8831                 cpstats.moves_left = 0;
8832
8833                 SendProgramStatsToFrontend( cps, &cpstats );
8834             }
8835         }
8836     }
8837 }
8838
8839
8840 /* Parse a game score from the character string "game", and
8841    record it as the history of the current game.  The game
8842    score is NOT assumed to start from the standard position.
8843    The display is not updated in any way.
8844    */
8845 void
8846 ParseGameHistory (char *game)
8847 {
8848     ChessMove moveType;
8849     int fromX, fromY, toX, toY, boardIndex;
8850     char promoChar;
8851     char *p, *q;
8852     char buf[MSG_SIZ];
8853
8854     if (appData.debugMode)
8855       fprintf(debugFP, "Parsing game history: %s\n", game);
8856
8857     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8858     gameInfo.site = StrSave(appData.icsHost);
8859     gameInfo.date = PGNDate();
8860     gameInfo.round = StrSave("-");
8861
8862     /* Parse out names of players */
8863     while (*game == ' ') game++;
8864     p = buf;
8865     while (*game != ' ') *p++ = *game++;
8866     *p = NULLCHAR;
8867     gameInfo.white = StrSave(buf);
8868     while (*game == ' ') game++;
8869     p = buf;
8870     while (*game != ' ' && *game != '\n') *p++ = *game++;
8871     *p = NULLCHAR;
8872     gameInfo.black = StrSave(buf);
8873
8874     /* Parse moves */
8875     boardIndex = blackPlaysFirst ? 1 : 0;
8876     yynewstr(game);
8877     for (;;) {
8878         yyboardindex = boardIndex;
8879         moveType = (ChessMove) Myylex();
8880         switch (moveType) {
8881           case IllegalMove:             /* maybe suicide chess, etc. */
8882   if (appData.debugMode) {
8883     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8884     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8885     setbuf(debugFP, NULL);
8886   }
8887           case WhitePromotion:
8888           case BlackPromotion:
8889           case WhiteNonPromotion:
8890           case BlackNonPromotion:
8891           case NormalMove:
8892           case WhiteCapturesEnPassant:
8893           case BlackCapturesEnPassant:
8894           case WhiteKingSideCastle:
8895           case WhiteQueenSideCastle:
8896           case BlackKingSideCastle:
8897           case BlackQueenSideCastle:
8898           case WhiteKingSideCastleWild:
8899           case WhiteQueenSideCastleWild:
8900           case BlackKingSideCastleWild:
8901           case BlackQueenSideCastleWild:
8902           /* PUSH Fabien */
8903           case WhiteHSideCastleFR:
8904           case WhiteASideCastleFR:
8905           case BlackHSideCastleFR:
8906           case BlackASideCastleFR:
8907           /* POP Fabien */
8908             fromX = currentMoveString[0] - AAA;
8909             fromY = currentMoveString[1] - ONE;
8910             toX = currentMoveString[2] - AAA;
8911             toY = currentMoveString[3] - ONE;
8912             promoChar = currentMoveString[4];
8913             break;
8914           case WhiteDrop:
8915           case BlackDrop:
8916             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8917             fromX = moveType == WhiteDrop ?
8918               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8919             (int) CharToPiece(ToLower(currentMoveString[0]));
8920             fromY = DROP_RANK;
8921             toX = currentMoveString[2] - AAA;
8922             toY = currentMoveString[3] - ONE;
8923             promoChar = NULLCHAR;
8924             break;
8925           case AmbiguousMove:
8926             /* bug? */
8927             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8928   if (appData.debugMode) {
8929     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8930     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8931     setbuf(debugFP, NULL);
8932   }
8933             DisplayError(buf, 0);
8934             return;
8935           case ImpossibleMove:
8936             /* bug? */
8937             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8938   if (appData.debugMode) {
8939     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8940     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8941     setbuf(debugFP, NULL);
8942   }
8943             DisplayError(buf, 0);
8944             return;
8945           case EndOfFile:
8946             if (boardIndex < backwardMostMove) {
8947                 /* Oops, gap.  How did that happen? */
8948                 DisplayError(_("Gap in move list"), 0);
8949                 return;
8950             }
8951             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8952             if (boardIndex > forwardMostMove) {
8953                 forwardMostMove = boardIndex;
8954             }
8955             return;
8956           case ElapsedTime:
8957             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8958                 strcat(parseList[boardIndex-1], " ");
8959                 strcat(parseList[boardIndex-1], yy_text);
8960             }
8961             continue;
8962           case Comment:
8963           case PGNTag:
8964           case NAG:
8965           default:
8966             /* ignore */
8967             continue;
8968           case WhiteWins:
8969           case BlackWins:
8970           case GameIsDrawn:
8971           case GameUnfinished:
8972             if (gameMode == IcsExamining) {
8973                 if (boardIndex < backwardMostMove) {
8974                     /* Oops, gap.  How did that happen? */
8975                     return;
8976                 }
8977                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8978                 return;
8979             }
8980             gameInfo.result = moveType;
8981             p = strchr(yy_text, '{');
8982             if (p == NULL) p = strchr(yy_text, '(');
8983             if (p == NULL) {
8984                 p = yy_text;
8985                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8986             } else {
8987                 q = strchr(p, *p == '{' ? '}' : ')');
8988                 if (q != NULL) *q = NULLCHAR;
8989                 p++;
8990             }
8991             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8992             gameInfo.resultDetails = StrSave(p);
8993             continue;
8994         }
8995         if (boardIndex >= forwardMostMove &&
8996             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8997             backwardMostMove = blackPlaysFirst ? 1 : 0;
8998             return;
8999         }
9000         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9001                                  fromY, fromX, toY, toX, promoChar,
9002                                  parseList[boardIndex]);
9003         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9004         /* currentMoveString is set as a side-effect of yylex */
9005         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9006         strcat(moveList[boardIndex], "\n");
9007         boardIndex++;
9008         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9009         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9010           case MT_NONE:
9011           case MT_STALEMATE:
9012           default:
9013             break;
9014           case MT_CHECK:
9015             if(gameInfo.variant != VariantShogi)
9016                 strcat(parseList[boardIndex - 1], "+");
9017             break;
9018           case MT_CHECKMATE:
9019           case MT_STAINMATE:
9020             strcat(parseList[boardIndex - 1], "#");
9021             break;
9022         }
9023     }
9024 }
9025
9026
9027 /* Apply a move to the given board  */
9028 void
9029 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9030 {
9031   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9032   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9033
9034     /* [HGM] compute & store e.p. status and castling rights for new position */
9035     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9036
9037       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9038       oldEP = (signed char)board[EP_STATUS];
9039       board[EP_STATUS] = EP_NONE;
9040
9041   if (fromY == DROP_RANK) {
9042         /* must be first */
9043         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9044             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9045             return;
9046         }
9047         piece = board[toY][toX] = (ChessSquare) fromX;
9048   } else {
9049       int i;
9050
9051       if( board[toY][toX] != EmptySquare )
9052            board[EP_STATUS] = EP_CAPTURE;
9053
9054       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9055            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9056                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9057       } else
9058       if( board[fromY][fromX] == WhitePawn ) {
9059            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9060                board[EP_STATUS] = EP_PAWN_MOVE;
9061            if( toY-fromY==2) {
9062                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9063                         gameInfo.variant != VariantBerolina || toX < fromX)
9064                       board[EP_STATUS] = toX | berolina;
9065                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9066                         gameInfo.variant != VariantBerolina || toX > fromX)
9067                       board[EP_STATUS] = toX;
9068            }
9069       } else
9070       if( board[fromY][fromX] == BlackPawn ) {
9071            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9072                board[EP_STATUS] = EP_PAWN_MOVE;
9073            if( toY-fromY== -2) {
9074                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9075                         gameInfo.variant != VariantBerolina || toX < fromX)
9076                       board[EP_STATUS] = toX | berolina;
9077                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9078                         gameInfo.variant != VariantBerolina || toX > fromX)
9079                       board[EP_STATUS] = toX;
9080            }
9081        }
9082
9083        for(i=0; i<nrCastlingRights; i++) {
9084            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9085               board[CASTLING][i] == toX   && castlingRank[i] == toY
9086              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9087        }
9088
9089      if (fromX == toX && fromY == toY) return;
9090
9091      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9092      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9093      if(gameInfo.variant == VariantKnightmate)
9094          king += (int) WhiteUnicorn - (int) WhiteKing;
9095
9096     /* Code added by Tord: */
9097     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9098     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9099         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9100       board[fromY][fromX] = EmptySquare;
9101       board[toY][toX] = EmptySquare;
9102       if((toX > fromX) != (piece == WhiteRook)) {
9103         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9104       } else {
9105         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9106       }
9107     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9108                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9109       board[fromY][fromX] = EmptySquare;
9110       board[toY][toX] = EmptySquare;
9111       if((toX > fromX) != (piece == BlackRook)) {
9112         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9113       } else {
9114         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9115       }
9116     /* End of code added by Tord */
9117
9118     } else if (board[fromY][fromX] == king
9119         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9120         && toY == fromY && toX > fromX+1) {
9121         board[fromY][fromX] = EmptySquare;
9122         board[toY][toX] = king;
9123         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9124         board[fromY][BOARD_RGHT-1] = EmptySquare;
9125     } else if (board[fromY][fromX] == king
9126         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9127                && toY == fromY && toX < fromX-1) {
9128         board[fromY][fromX] = EmptySquare;
9129         board[toY][toX] = king;
9130         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9131         board[fromY][BOARD_LEFT] = EmptySquare;
9132     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9133                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9134                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9135                ) {
9136         /* white pawn promotion */
9137         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9138         if(gameInfo.variant==VariantBughouse ||
9139            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9140             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9141         board[fromY][fromX] = EmptySquare;
9142     } else if ((fromY >= BOARD_HEIGHT>>1)
9143                && (toX != fromX)
9144                && gameInfo.variant != VariantXiangqi
9145                && gameInfo.variant != VariantBerolina
9146                && (board[fromY][fromX] == WhitePawn)
9147                && (board[toY][toX] == EmptySquare)) {
9148         board[fromY][fromX] = EmptySquare;
9149         board[toY][toX] = WhitePawn;
9150         captured = board[toY - 1][toX];
9151         board[toY - 1][toX] = EmptySquare;
9152     } else if ((fromY == BOARD_HEIGHT-4)
9153                && (toX == fromX)
9154                && gameInfo.variant == VariantBerolina
9155                && (board[fromY][fromX] == WhitePawn)
9156                && (board[toY][toX] == EmptySquare)) {
9157         board[fromY][fromX] = EmptySquare;
9158         board[toY][toX] = WhitePawn;
9159         if(oldEP & EP_BEROLIN_A) {
9160                 captured = board[fromY][fromX-1];
9161                 board[fromY][fromX-1] = EmptySquare;
9162         }else{  captured = board[fromY][fromX+1];
9163                 board[fromY][fromX+1] = EmptySquare;
9164         }
9165     } else if (board[fromY][fromX] == king
9166         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9167                && toY == fromY && toX > fromX+1) {
9168         board[fromY][fromX] = EmptySquare;
9169         board[toY][toX] = king;
9170         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9171         board[fromY][BOARD_RGHT-1] = EmptySquare;
9172     } else if (board[fromY][fromX] == king
9173         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9174                && toY == fromY && toX < fromX-1) {
9175         board[fromY][fromX] = EmptySquare;
9176         board[toY][toX] = king;
9177         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9178         board[fromY][BOARD_LEFT] = EmptySquare;
9179     } else if (fromY == 7 && fromX == 3
9180                && board[fromY][fromX] == BlackKing
9181                && toY == 7 && toX == 5) {
9182         board[fromY][fromX] = EmptySquare;
9183         board[toY][toX] = BlackKing;
9184         board[fromY][7] = EmptySquare;
9185         board[toY][4] = BlackRook;
9186     } else if (fromY == 7 && fromX == 3
9187                && board[fromY][fromX] == BlackKing
9188                && toY == 7 && toX == 1) {
9189         board[fromY][fromX] = EmptySquare;
9190         board[toY][toX] = BlackKing;
9191         board[fromY][0] = EmptySquare;
9192         board[toY][2] = BlackRook;
9193     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9194                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9195                && toY < promoRank && promoChar
9196                ) {
9197         /* black pawn promotion */
9198         board[toY][toX] = CharToPiece(ToLower(promoChar));
9199         if(gameInfo.variant==VariantBughouse ||
9200            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9201             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9202         board[fromY][fromX] = EmptySquare;
9203     } else if ((fromY < BOARD_HEIGHT>>1)
9204                && (toX != fromX)
9205                && gameInfo.variant != VariantXiangqi
9206                && gameInfo.variant != VariantBerolina
9207                && (board[fromY][fromX] == BlackPawn)
9208                && (board[toY][toX] == EmptySquare)) {
9209         board[fromY][fromX] = EmptySquare;
9210         board[toY][toX] = BlackPawn;
9211         captured = board[toY + 1][toX];
9212         board[toY + 1][toX] = EmptySquare;
9213     } else if ((fromY == 3)
9214                && (toX == fromX)
9215                && gameInfo.variant == VariantBerolina
9216                && (board[fromY][fromX] == BlackPawn)
9217                && (board[toY][toX] == EmptySquare)) {
9218         board[fromY][fromX] = EmptySquare;
9219         board[toY][toX] = BlackPawn;
9220         if(oldEP & EP_BEROLIN_A) {
9221                 captured = board[fromY][fromX-1];
9222                 board[fromY][fromX-1] = EmptySquare;
9223         }else{  captured = board[fromY][fromX+1];
9224                 board[fromY][fromX+1] = EmptySquare;
9225         }
9226     } else {
9227         board[toY][toX] = board[fromY][fromX];
9228         board[fromY][fromX] = EmptySquare;
9229     }
9230   }
9231
9232     if (gameInfo.holdingsWidth != 0) {
9233
9234       /* !!A lot more code needs to be written to support holdings  */
9235       /* [HGM] OK, so I have written it. Holdings are stored in the */
9236       /* penultimate board files, so they are automaticlly stored   */
9237       /* in the game history.                                       */
9238       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9239                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9240         /* Delete from holdings, by decreasing count */
9241         /* and erasing image if necessary            */
9242         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9243         if(p < (int) BlackPawn) { /* white drop */
9244              p -= (int)WhitePawn;
9245                  p = PieceToNumber((ChessSquare)p);
9246              if(p >= gameInfo.holdingsSize) p = 0;
9247              if(--board[p][BOARD_WIDTH-2] <= 0)
9248                   board[p][BOARD_WIDTH-1] = EmptySquare;
9249              if((int)board[p][BOARD_WIDTH-2] < 0)
9250                         board[p][BOARD_WIDTH-2] = 0;
9251         } else {                  /* black drop */
9252              p -= (int)BlackPawn;
9253                  p = PieceToNumber((ChessSquare)p);
9254              if(p >= gameInfo.holdingsSize) p = 0;
9255              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9256                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9257              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9258                         board[BOARD_HEIGHT-1-p][1] = 0;
9259         }
9260       }
9261       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9262           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9263         /* [HGM] holdings: Add to holdings, if holdings exist */
9264         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9265                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9266                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9267         }
9268         p = (int) captured;
9269         if (p >= (int) BlackPawn) {
9270           p -= (int)BlackPawn;
9271           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9272                   /* in Shogi restore piece to its original  first */
9273                   captured = (ChessSquare) (DEMOTED captured);
9274                   p = DEMOTED p;
9275           }
9276           p = PieceToNumber((ChessSquare)p);
9277           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9278           board[p][BOARD_WIDTH-2]++;
9279           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9280         } else {
9281           p -= (int)WhitePawn;
9282           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9283                   captured = (ChessSquare) (DEMOTED captured);
9284                   p = DEMOTED p;
9285           }
9286           p = PieceToNumber((ChessSquare)p);
9287           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9288           board[BOARD_HEIGHT-1-p][1]++;
9289           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9290         }
9291       }
9292     } else if (gameInfo.variant == VariantAtomic) {
9293       if (captured != EmptySquare) {
9294         int y, x;
9295         for (y = toY-1; y <= toY+1; y++) {
9296           for (x = toX-1; x <= toX+1; x++) {
9297             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9298                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9299               board[y][x] = EmptySquare;
9300             }
9301           }
9302         }
9303         board[toY][toX] = EmptySquare;
9304       }
9305     }
9306     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9307         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9308     } else
9309     if(promoChar == '+') {
9310         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9311         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9312     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9313         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9314     }
9315     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9316                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9317         // [HGM] superchess: take promotion piece out of holdings
9318         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9319         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9320             if(!--board[k][BOARD_WIDTH-2])
9321                 board[k][BOARD_WIDTH-1] = EmptySquare;
9322         } else {
9323             if(!--board[BOARD_HEIGHT-1-k][1])
9324                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9325         }
9326     }
9327
9328 }
9329
9330 /* Updates forwardMostMove */
9331 void
9332 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9333 {
9334 //    forwardMostMove++; // [HGM] bare: moved downstream
9335
9336     (void) CoordsToAlgebraic(boards[forwardMostMove],
9337                              PosFlags(forwardMostMove),
9338                              fromY, fromX, toY, toX, promoChar,
9339                              parseList[forwardMostMove]);
9340
9341     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9342         int timeLeft; static int lastLoadFlag=0; int king, piece;
9343         piece = boards[forwardMostMove][fromY][fromX];
9344         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9345         if(gameInfo.variant == VariantKnightmate)
9346             king += (int) WhiteUnicorn - (int) WhiteKing;
9347         if(forwardMostMove == 0) {
9348             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9349                 fprintf(serverMoves, "%s;", UserName());
9350             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9351                 fprintf(serverMoves, "%s;", second.tidy);
9352             fprintf(serverMoves, "%s;", first.tidy);
9353             if(gameMode == MachinePlaysWhite)
9354                 fprintf(serverMoves, "%s;", UserName());
9355             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9356                 fprintf(serverMoves, "%s;", second.tidy);
9357         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9358         lastLoadFlag = loadFlag;
9359         // print base move
9360         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9361         // print castling suffix
9362         if( toY == fromY && piece == king ) {
9363             if(toX-fromX > 1)
9364                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9365             if(fromX-toX >1)
9366                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9367         }
9368         // e.p. suffix
9369         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9370              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9371              boards[forwardMostMove][toY][toX] == EmptySquare
9372              && fromX != toX && fromY != toY)
9373                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9374         // promotion suffix
9375         if(promoChar != NULLCHAR)
9376                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9377         if(!loadFlag) {
9378                 char buf[MOVE_LEN*2], *p; int len;
9379             fprintf(serverMoves, "/%d/%d",
9380                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9381             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9382             else                      timeLeft = blackTimeRemaining/1000;
9383             fprintf(serverMoves, "/%d", timeLeft);
9384                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9385                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9386                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9387             fprintf(serverMoves, "/%s", buf);
9388         }
9389         fflush(serverMoves);
9390     }
9391
9392     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9393         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9394       return;
9395     }
9396     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9397     if (commentList[forwardMostMove+1] != NULL) {
9398         free(commentList[forwardMostMove+1]);
9399         commentList[forwardMostMove+1] = NULL;
9400     }
9401     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9402     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9403     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9404     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9405     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9406     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9407     adjustedClock = FALSE;
9408     gameInfo.result = GameUnfinished;
9409     if (gameInfo.resultDetails != NULL) {
9410         free(gameInfo.resultDetails);
9411         gameInfo.resultDetails = NULL;
9412     }
9413     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9414                               moveList[forwardMostMove - 1]);
9415     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9416       case MT_NONE:
9417       case MT_STALEMATE:
9418       default:
9419         break;
9420       case MT_CHECK:
9421         if(gameInfo.variant != VariantShogi)
9422             strcat(parseList[forwardMostMove - 1], "+");
9423         break;
9424       case MT_CHECKMATE:
9425       case MT_STAINMATE:
9426         strcat(parseList[forwardMostMove - 1], "#");
9427         break;
9428     }
9429     if (appData.debugMode) {
9430         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9431     }
9432
9433 }
9434
9435 /* Updates currentMove if not pausing */
9436 void
9437 ShowMove (int fromX, int fromY, int toX, int toY)
9438 {
9439     int instant = (gameMode == PlayFromGameFile) ?
9440         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9441     if(appData.noGUI) return;
9442     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9443         if (!instant) {
9444             if (forwardMostMove == currentMove + 1) {
9445                 AnimateMove(boards[forwardMostMove - 1],
9446                             fromX, fromY, toX, toY);
9447             }
9448             if (appData.highlightLastMove) {
9449                 SetHighlights(fromX, fromY, toX, toY);
9450             }
9451         }
9452         currentMove = forwardMostMove;
9453     }
9454
9455     if (instant) return;
9456
9457     DisplayMove(currentMove - 1);
9458     DrawPosition(FALSE, boards[currentMove]);
9459     DisplayBothClocks();
9460     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9461 }
9462
9463 void
9464 SendEgtPath (ChessProgramState *cps)
9465 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9466         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9467
9468         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9469
9470         while(*p) {
9471             char c, *q = name+1, *r, *s;
9472
9473             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9474             while(*p && *p != ',') *q++ = *p++;
9475             *q++ = ':'; *q = 0;
9476             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9477                 strcmp(name, ",nalimov:") == 0 ) {
9478                 // take nalimov path from the menu-changeable option first, if it is defined
9479               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9480                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9481             } else
9482             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9483                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9484                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9485                 s = r = StrStr(s, ":") + 1; // beginning of path info
9486                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9487                 c = *r; *r = 0;             // temporarily null-terminate path info
9488                     *--q = 0;               // strip of trailig ':' from name
9489                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9490                 *r = c;
9491                 SendToProgram(buf,cps);     // send egtbpath command for this format
9492             }
9493             if(*p == ',') p++; // read away comma to position for next format name
9494         }
9495 }
9496
9497 void
9498 InitChessProgram (ChessProgramState *cps, int setup)
9499 /* setup needed to setup FRC opening position */
9500 {
9501     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9502     if (appData.noChessProgram) return;
9503     hintRequested = FALSE;
9504     bookRequested = FALSE;
9505
9506     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9507     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9508     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9509     if(cps->memSize) { /* [HGM] memory */
9510       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9511         SendToProgram(buf, cps);
9512     }
9513     SendEgtPath(cps); /* [HGM] EGT */
9514     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9515       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9516         SendToProgram(buf, cps);
9517     }
9518
9519     SendToProgram(cps->initString, cps);
9520     if (gameInfo.variant != VariantNormal &&
9521         gameInfo.variant != VariantLoadable
9522         /* [HGM] also send variant if board size non-standard */
9523         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9524                                             ) {
9525       char *v = VariantName(gameInfo.variant);
9526       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9527         /* [HGM] in protocol 1 we have to assume all variants valid */
9528         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9529         DisplayFatalError(buf, 0, 1);
9530         return;
9531       }
9532
9533       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9534       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9535       if( gameInfo.variant == VariantXiangqi )
9536            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9537       if( gameInfo.variant == VariantShogi )
9538            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9539       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9540            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9541       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9542           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9543            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9544       if( gameInfo.variant == VariantCourier )
9545            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9546       if( gameInfo.variant == VariantSuper )
9547            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9548       if( gameInfo.variant == VariantGreat )
9549            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9550       if( gameInfo.variant == VariantSChess )
9551            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9552       if( gameInfo.variant == VariantGrand )
9553            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9554
9555       if(overruled) {
9556         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9557                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9558            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9559            if(StrStr(cps->variants, b) == NULL) {
9560                // specific sized variant not known, check if general sizing allowed
9561                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9562                    if(StrStr(cps->variants, "boardsize") == NULL) {
9563                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9564                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9565                        DisplayFatalError(buf, 0, 1);
9566                        return;
9567                    }
9568                    /* [HGM] here we really should compare with the maximum supported board size */
9569                }
9570            }
9571       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9572       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9573       SendToProgram(buf, cps);
9574     }
9575     currentlyInitializedVariant = gameInfo.variant;
9576
9577     /* [HGM] send opening position in FRC to first engine */
9578     if(setup) {
9579           SendToProgram("force\n", cps);
9580           SendBoard(cps, 0);
9581           /* engine is now in force mode! Set flag to wake it up after first move. */
9582           setboardSpoiledMachineBlack = 1;
9583     }
9584
9585     if (cps->sendICS) {
9586       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9587       SendToProgram(buf, cps);
9588     }
9589     cps->maybeThinking = FALSE;
9590     cps->offeredDraw = 0;
9591     if (!appData.icsActive) {
9592         SendTimeControl(cps, movesPerSession, timeControl,
9593                         timeIncrement, appData.searchDepth,
9594                         searchTime);
9595     }
9596     if (appData.showThinking
9597         // [HGM] thinking: four options require thinking output to be sent
9598         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9599                                 ) {
9600         SendToProgram("post\n", cps);
9601     }
9602     SendToProgram("hard\n", cps);
9603     if (!appData.ponderNextMove) {
9604         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9605            it without being sure what state we are in first.  "hard"
9606            is not a toggle, so that one is OK.
9607          */
9608         SendToProgram("easy\n", cps);
9609     }
9610     if (cps->usePing) {
9611       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9612       SendToProgram(buf, cps);
9613     }
9614     cps->initDone = TRUE;
9615     ClearEngineOutputPane(cps == &second);
9616 }
9617
9618
9619 void
9620 StartChessProgram (ChessProgramState *cps)
9621 {
9622     char buf[MSG_SIZ];
9623     int err;
9624
9625     if (appData.noChessProgram) return;
9626     cps->initDone = FALSE;
9627
9628     if (strcmp(cps->host, "localhost") == 0) {
9629         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9630     } else if (*appData.remoteShell == NULLCHAR) {
9631         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9632     } else {
9633         if (*appData.remoteUser == NULLCHAR) {
9634           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9635                     cps->program);
9636         } else {
9637           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9638                     cps->host, appData.remoteUser, cps->program);
9639         }
9640         err = StartChildProcess(buf, "", &cps->pr);
9641     }
9642
9643     if (err != 0) {
9644       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9645         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9646         if(cps != &first) return;
9647         appData.noChessProgram = TRUE;
9648         ThawUI();
9649         SetNCPMode();
9650 //      DisplayFatalError(buf, err, 1);
9651 //      cps->pr = NoProc;
9652 //      cps->isr = NULL;
9653         return;
9654     }
9655
9656     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9657     if (cps->protocolVersion > 1) {
9658       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9659       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9660       cps->comboCnt = 0;  //                and values of combo boxes
9661       SendToProgram(buf, cps);
9662     } else {
9663       SendToProgram("xboard\n", cps);
9664     }
9665 }
9666
9667 void
9668 TwoMachinesEventIfReady P((void))
9669 {
9670   static int curMess = 0;
9671   if (first.lastPing != first.lastPong) {
9672     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9673     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9674     return;
9675   }
9676   if (second.lastPing != second.lastPong) {
9677     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9678     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9679     return;
9680   }
9681   DisplayMessage("", ""); curMess = 0;
9682   ThawUI();
9683   TwoMachinesEvent();
9684 }
9685
9686 char *
9687 MakeName (char *template)
9688 {
9689     time_t clock;
9690     struct tm *tm;
9691     static char buf[MSG_SIZ];
9692     char *p = buf;
9693     int i;
9694
9695     clock = time((time_t *)NULL);
9696     tm = localtime(&clock);
9697
9698     while(*p++ = *template++) if(p[-1] == '%') {
9699         switch(*template++) {
9700           case 0:   *p = 0; return buf;
9701           case 'Y': i = tm->tm_year+1900; break;
9702           case 'y': i = tm->tm_year-100; break;
9703           case 'M': i = tm->tm_mon+1; break;
9704           case 'd': i = tm->tm_mday; break;
9705           case 'h': i = tm->tm_hour; break;
9706           case 'm': i = tm->tm_min; break;
9707           case 's': i = tm->tm_sec; break;
9708           default:  i = 0;
9709         }
9710         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9711     }
9712     return buf;
9713 }
9714
9715 int
9716 CountPlayers (char *p)
9717 {
9718     int n = 0;
9719     while(p = strchr(p, '\n')) p++, n++; // count participants
9720     return n;
9721 }
9722
9723 FILE *
9724 WriteTourneyFile (char *results, FILE *f)
9725 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9726     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9727     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9728         // create a file with tournament description
9729         fprintf(f, "-participants {%s}\n", appData.participants);
9730         fprintf(f, "-seedBase %d\n", appData.seedBase);
9731         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9732         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9733         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9734         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9735         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9736         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9737         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9738         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9739         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9740         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9741         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9742         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9743         if(searchTime > 0)
9744                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9745         else {
9746                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9747                 fprintf(f, "-tc %s\n", appData.timeControl);
9748                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9749         }
9750         fprintf(f, "-results \"%s\"\n", results);
9751     }
9752     return f;
9753 }
9754
9755 #define MAXENGINES 1000
9756 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9757
9758 void
9759 Substitute (char *participants, int expunge)
9760 {
9761     int i, changed, changes=0, nPlayers=0;
9762     char *p, *q, *r, buf[MSG_SIZ];
9763     if(participants == NULL) return;
9764     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9765     r = p = participants; q = appData.participants;
9766     while(*p && *p == *q) {
9767         if(*p == '\n') r = p+1, nPlayers++;
9768         p++; q++;
9769     }
9770     if(*p) { // difference
9771         while(*p && *p++ != '\n');
9772         while(*q && *q++ != '\n');
9773       changed = nPlayers;
9774         changes = 1 + (strcmp(p, q) != 0);
9775     }
9776     if(changes == 1) { // a single engine mnemonic was changed
9777         q = r; while(*q) nPlayers += (*q++ == '\n');
9778         p = buf; while(*r && (*p = *r++) != '\n') p++;
9779         *p = NULLCHAR;
9780         NamesToList(firstChessProgramNames, command, mnemonic);
9781         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9782         if(mnemonic[i]) { // The substitute is valid
9783             FILE *f;
9784             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9785                 flock(fileno(f), LOCK_EX);
9786                 ParseArgsFromFile(f);
9787                 fseek(f, 0, SEEK_SET);
9788                 FREE(appData.participants); appData.participants = participants;
9789                 if(expunge) { // erase results of replaced engine
9790                     int len = strlen(appData.results), w, b, dummy;
9791                     for(i=0; i<len; i++) {
9792                         Pairing(i, nPlayers, &w, &b, &dummy);
9793                         if((w == changed || b == changed) && appData.results[i] == '*') {
9794                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9795                             fclose(f);
9796                             return;
9797                         }
9798                     }
9799                     for(i=0; i<len; i++) {
9800                         Pairing(i, nPlayers, &w, &b, &dummy);
9801                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9802                     }
9803                 }
9804                 WriteTourneyFile(appData.results, f);
9805                 fclose(f); // release lock
9806                 return;
9807             }
9808         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9809     }
9810     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9811     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9812     free(participants);
9813     return;
9814 }
9815
9816 int
9817 CreateTourney (char *name)
9818 {
9819         FILE *f;
9820         if(matchMode && strcmp(name, appData.tourneyFile)) {
9821              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9822         }
9823         if(name[0] == NULLCHAR) {
9824             if(appData.participants[0])
9825                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9826             return 0;
9827         }
9828         f = fopen(name, "r");
9829         if(f) { // file exists
9830             ASSIGN(appData.tourneyFile, name);
9831             ParseArgsFromFile(f); // parse it
9832         } else {
9833             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9834             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9835                 DisplayError(_("Not enough participants"), 0);
9836                 return 0;
9837             }
9838             ASSIGN(appData.tourneyFile, name);
9839             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9840             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9841         }
9842         fclose(f);
9843         appData.noChessProgram = FALSE;
9844         appData.clockMode = TRUE;
9845         SetGNUMode();
9846         return 1;
9847 }
9848
9849 void
9850 NamesToList (char *names, char **engineList, char **engineMnemonic)
9851 {
9852     char buf[MSG_SIZ], *p, *q;
9853     int i=1;
9854     while(*names) {
9855         p = names; q = buf;
9856         while(*p && *p != '\n') *q++ = *p++;
9857         *q = 0;
9858         if(engineList[i]) free(engineList[i]);
9859         engineList[i] = strdup(buf);
9860         if(*p == '\n') p++;
9861         TidyProgramName(engineList[i], "localhost", buf);
9862         if(engineMnemonic[i]) free(engineMnemonic[i]);
9863         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9864             strcat(buf, " (");
9865             sscanf(q + 8, "%s", buf + strlen(buf));
9866             strcat(buf, ")");
9867         }
9868         engineMnemonic[i] = strdup(buf);
9869         names = p; i++;
9870       if(i > MAXENGINES - 2) break;
9871     }
9872     engineList[i] = engineMnemonic[i] = NULL;
9873 }
9874
9875 // following implemented as macro to avoid type limitations
9876 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9877
9878 void
9879 SwapEngines (int n)
9880 {   // swap settings for first engine and other engine (so far only some selected options)
9881     int h;
9882     char *p;
9883     if(n == 0) return;
9884     SWAP(directory, p)
9885     SWAP(chessProgram, p)
9886     SWAP(isUCI, h)
9887     SWAP(hasOwnBookUCI, h)
9888     SWAP(protocolVersion, h)
9889     SWAP(reuse, h)
9890     SWAP(scoreIsAbsolute, h)
9891     SWAP(timeOdds, h)
9892     SWAP(logo, p)
9893     SWAP(pgnName, p)
9894     SWAP(pvSAN, h)
9895     SWAP(engOptions, p)
9896 }
9897
9898 void
9899 SetPlayer (int player)
9900 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9901     int i;
9902     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9903     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9904     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9905     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9906     if(mnemonic[i]) {
9907         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9908         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9909         appData.firstHasOwnBookUCI = !appData.defNoBook;
9910         ParseArgsFromString(buf);
9911     }
9912     free(engineName);
9913 }
9914
9915 int
9916 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9917 {   // determine players from game number
9918     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9919
9920     if(appData.tourneyType == 0) {
9921         roundsPerCycle = (nPlayers - 1) | 1;
9922         pairingsPerRound = nPlayers / 2;
9923     } else if(appData.tourneyType > 0) {
9924         roundsPerCycle = nPlayers - appData.tourneyType;
9925         pairingsPerRound = appData.tourneyType;
9926     }
9927     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9928     gamesPerCycle = gamesPerRound * roundsPerCycle;
9929     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9930     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9931     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9932     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9933     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9934     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9935
9936     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9937     if(appData.roundSync) *syncInterval = gamesPerRound;
9938
9939     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9940
9941     if(appData.tourneyType == 0) {
9942         if(curPairing == (nPlayers-1)/2 ) {
9943             *whitePlayer = curRound;
9944             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9945         } else {
9946             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9947             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9948             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9949             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9950         }
9951     } else if(appData.tourneyType > 0) {
9952         *whitePlayer = curPairing;
9953         *blackPlayer = curRound + appData.tourneyType;
9954     }
9955
9956     // take care of white/black alternation per round. 
9957     // For cycles and games this is already taken care of by default, derived from matchGame!
9958     return curRound & 1;
9959 }
9960
9961 int
9962 NextTourneyGame (int nr, int *swapColors)
9963 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9964     char *p, *q;
9965     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9966     FILE *tf;
9967     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9968     tf = fopen(appData.tourneyFile, "r");
9969     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9970     ParseArgsFromFile(tf); fclose(tf);
9971     InitTimeControls(); // TC might be altered from tourney file
9972
9973     nPlayers = CountPlayers(appData.participants); // count participants
9974     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
9975     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9976
9977     if(syncInterval) {
9978         p = q = appData.results;
9979         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9980         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9981             DisplayMessage(_("Waiting for other game(s)"),"");
9982             waitingForGame = TRUE;
9983             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9984             return 0;
9985         }
9986         waitingForGame = FALSE;
9987     }
9988
9989     if(appData.tourneyType < 0) {
9990         if(nr>=0 && !pairingReceived) {
9991             char buf[1<<16];
9992             if(pairing.pr == NoProc) {
9993                 if(!appData.pairingEngine[0]) {
9994                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
9995                     return 0;
9996                 }
9997                 StartChessProgram(&pairing); // starts the pairing engine
9998             }
9999             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10000             SendToProgram(buf, &pairing);
10001             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10002             SendToProgram(buf, &pairing);
10003             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10004         }
10005         pairingReceived = 0;                              // ... so we continue here 
10006         *swapColors = 0;
10007         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10008         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10009         matchGame = 1; roundNr = nr / syncInterval + 1;
10010     }
10011
10012     if(first.pr != NoProc || second.pr != NoProc) return 1; // engines already loaded
10013
10014     // redefine engines, engine dir, etc.
10015     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10016     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
10017     SwapEngines(1);
10018     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
10019     SwapEngines(1);         // and make that valid for second engine by swapping
10020     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10021     InitEngine(&second, 1);
10022     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10023     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10024     return 1;
10025 }
10026
10027 void
10028 NextMatchGame ()
10029 {   // performs game initialization that does not invoke engines, and then tries to start the game
10030     int res, firstWhite, swapColors = 0;
10031     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10032     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10033     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10034     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10035     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10036     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10037     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10038     Reset(FALSE, first.pr != NoProc);
10039     res = LoadGameOrPosition(matchGame); // setup game
10040     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10041     if(!res) return; // abort when bad game/pos file
10042     TwoMachinesEvent();
10043 }
10044
10045 void
10046 UserAdjudicationEvent (int result)
10047 {
10048     ChessMove gameResult = GameIsDrawn;
10049
10050     if( result > 0 ) {
10051         gameResult = WhiteWins;
10052     }
10053     else if( result < 0 ) {
10054         gameResult = BlackWins;
10055     }
10056
10057     if( gameMode == TwoMachinesPlay ) {
10058         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10059     }
10060 }
10061
10062
10063 // [HGM] save: calculate checksum of game to make games easily identifiable
10064 int
10065 StringCheckSum (char *s)
10066 {
10067         int i = 0;
10068         if(s==NULL) return 0;
10069         while(*s) i = i*259 + *s++;
10070         return i;
10071 }
10072
10073 int
10074 GameCheckSum ()
10075 {
10076         int i, sum=0;
10077         for(i=backwardMostMove; i<forwardMostMove; i++) {
10078                 sum += pvInfoList[i].depth;
10079                 sum += StringCheckSum(parseList[i]);
10080                 sum += StringCheckSum(commentList[i]);
10081                 sum *= 261;
10082         }
10083         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10084         return sum + StringCheckSum(commentList[i]);
10085 } // end of save patch
10086
10087 void
10088 GameEnds (ChessMove result, char *resultDetails, int whosays)
10089 {
10090     GameMode nextGameMode;
10091     int isIcsGame;
10092     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10093
10094     if(endingGame) return; /* [HGM] crash: forbid recursion */
10095     endingGame = 1;
10096     if(twoBoards) { // [HGM] dual: switch back to one board
10097         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10098         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10099     }
10100     if (appData.debugMode) {
10101       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10102               result, resultDetails ? resultDetails : "(null)", whosays);
10103     }
10104
10105     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10106
10107     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10108         /* If we are playing on ICS, the server decides when the
10109            game is over, but the engine can offer to draw, claim
10110            a draw, or resign.
10111          */
10112 #if ZIPPY
10113         if (appData.zippyPlay && first.initDone) {
10114             if (result == GameIsDrawn) {
10115                 /* In case draw still needs to be claimed */
10116                 SendToICS(ics_prefix);
10117                 SendToICS("draw\n");
10118             } else if (StrCaseStr(resultDetails, "resign")) {
10119                 SendToICS(ics_prefix);
10120                 SendToICS("resign\n");
10121             }
10122         }
10123 #endif
10124         endingGame = 0; /* [HGM] crash */
10125         return;
10126     }
10127
10128     /* If we're loading the game from a file, stop */
10129     if (whosays == GE_FILE) {
10130       (void) StopLoadGameTimer();
10131       gameFileFP = NULL;
10132     }
10133
10134     /* Cancel draw offers */
10135     first.offeredDraw = second.offeredDraw = 0;
10136
10137     /* If this is an ICS game, only ICS can really say it's done;
10138        if not, anyone can. */
10139     isIcsGame = (gameMode == IcsPlayingWhite ||
10140                  gameMode == IcsPlayingBlack ||
10141                  gameMode == IcsObserving    ||
10142                  gameMode == IcsExamining);
10143
10144     if (!isIcsGame || whosays == GE_ICS) {
10145         /* OK -- not an ICS game, or ICS said it was done */
10146         StopClocks();
10147         if (!isIcsGame && !appData.noChessProgram)
10148           SetUserThinkingEnables();
10149
10150         /* [HGM] if a machine claims the game end we verify this claim */
10151         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10152             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10153                 char claimer;
10154                 ChessMove trueResult = (ChessMove) -1;
10155
10156                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10157                                             first.twoMachinesColor[0] :
10158                                             second.twoMachinesColor[0] ;
10159
10160                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10161                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10162                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10163                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10164                 } else
10165                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10166                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10167                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10168                 } else
10169                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10170                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10171                 }
10172
10173                 // now verify win claims, but not in drop games, as we don't understand those yet
10174                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10175                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10176                     (result == WhiteWins && claimer == 'w' ||
10177                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10178                       if (appData.debugMode) {
10179                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10180                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10181                       }
10182                       if(result != trueResult) {
10183                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10184                               result = claimer == 'w' ? BlackWins : WhiteWins;
10185                               resultDetails = buf;
10186                       }
10187                 } else
10188                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10189                     && (forwardMostMove <= backwardMostMove ||
10190                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10191                         (claimer=='b')==(forwardMostMove&1))
10192                                                                                   ) {
10193                       /* [HGM] verify: draws that were not flagged are false claims */
10194                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10195                       result = claimer == 'w' ? BlackWins : WhiteWins;
10196                       resultDetails = buf;
10197                 }
10198                 /* (Claiming a loss is accepted no questions asked!) */
10199             }
10200             /* [HGM] bare: don't allow bare King to win */
10201             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10202                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10203                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10204                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10205                && result != GameIsDrawn)
10206             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10207                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10208                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10209                         if(p >= 0 && p <= (int)WhiteKing) k++;
10210                 }
10211                 if (appData.debugMode) {
10212                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10213                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10214                 }
10215                 if(k <= 1) {
10216                         result = GameIsDrawn;
10217                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10218                         resultDetails = buf;
10219                 }
10220             }
10221         }
10222
10223
10224         if(serverMoves != NULL && !loadFlag) { char c = '=';
10225             if(result==WhiteWins) c = '+';
10226             if(result==BlackWins) c = '-';
10227             if(resultDetails != NULL)
10228                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10229         }
10230         if (resultDetails != NULL) {
10231             gameInfo.result = result;
10232             gameInfo.resultDetails = StrSave(resultDetails);
10233
10234             /* display last move only if game was not loaded from file */
10235             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10236                 DisplayMove(currentMove - 1);
10237
10238             if (forwardMostMove != 0) {
10239                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10240                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10241                                                                 ) {
10242                     if (*appData.saveGameFile != NULLCHAR) {
10243                         SaveGameToFile(appData.saveGameFile, TRUE);
10244                     } else if (appData.autoSaveGames) {
10245                         AutoSaveGame();
10246                     }
10247                     if (*appData.savePositionFile != NULLCHAR) {
10248                         SavePositionToFile(appData.savePositionFile);
10249                     }
10250                 }
10251             }
10252
10253             /* Tell program how game ended in case it is learning */
10254             /* [HGM] Moved this to after saving the PGN, just in case */
10255             /* engine died and we got here through time loss. In that */
10256             /* case we will get a fatal error writing the pipe, which */
10257             /* would otherwise lose us the PGN.                       */
10258             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10259             /* output during GameEnds should never be fatal anymore   */
10260             if (gameMode == MachinePlaysWhite ||
10261                 gameMode == MachinePlaysBlack ||
10262                 gameMode == TwoMachinesPlay ||
10263                 gameMode == IcsPlayingWhite ||
10264                 gameMode == IcsPlayingBlack ||
10265                 gameMode == BeginningOfGame) {
10266                 char buf[MSG_SIZ];
10267                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10268                         resultDetails);
10269                 if (first.pr != NoProc) {
10270                     SendToProgram(buf, &first);
10271                 }
10272                 if (second.pr != NoProc &&
10273                     gameMode == TwoMachinesPlay) {
10274                     SendToProgram(buf, &second);
10275                 }
10276             }
10277         }
10278
10279         if (appData.icsActive) {
10280             if (appData.quietPlay &&
10281                 (gameMode == IcsPlayingWhite ||
10282                  gameMode == IcsPlayingBlack)) {
10283                 SendToICS(ics_prefix);
10284                 SendToICS("set shout 1\n");
10285             }
10286             nextGameMode = IcsIdle;
10287             ics_user_moved = FALSE;
10288             /* clean up premove.  It's ugly when the game has ended and the
10289              * premove highlights are still on the board.
10290              */
10291             if (gotPremove) {
10292               gotPremove = FALSE;
10293               ClearPremoveHighlights();
10294               DrawPosition(FALSE, boards[currentMove]);
10295             }
10296             if (whosays == GE_ICS) {
10297                 switch (result) {
10298                 case WhiteWins:
10299                     if (gameMode == IcsPlayingWhite)
10300                         PlayIcsWinSound();
10301                     else if(gameMode == IcsPlayingBlack)
10302                         PlayIcsLossSound();
10303                     break;
10304                 case BlackWins:
10305                     if (gameMode == IcsPlayingBlack)
10306                         PlayIcsWinSound();
10307                     else if(gameMode == IcsPlayingWhite)
10308                         PlayIcsLossSound();
10309                     break;
10310                 case GameIsDrawn:
10311                     PlayIcsDrawSound();
10312                     break;
10313                 default:
10314                     PlayIcsUnfinishedSound();
10315                 }
10316             }
10317         } else if (gameMode == EditGame ||
10318                    gameMode == PlayFromGameFile ||
10319                    gameMode == AnalyzeMode ||
10320                    gameMode == AnalyzeFile) {
10321             nextGameMode = gameMode;
10322         } else {
10323             nextGameMode = EndOfGame;
10324         }
10325         pausing = FALSE;
10326         ModeHighlight();
10327     } else {
10328         nextGameMode = gameMode;
10329     }
10330
10331     if (appData.noChessProgram) {
10332         gameMode = nextGameMode;
10333         ModeHighlight();
10334         endingGame = 0; /* [HGM] crash */
10335         return;
10336     }
10337
10338     if (first.reuse) {
10339         /* Put first chess program into idle state */
10340         if (first.pr != NoProc &&
10341             (gameMode == MachinePlaysWhite ||
10342              gameMode == MachinePlaysBlack ||
10343              gameMode == TwoMachinesPlay ||
10344              gameMode == IcsPlayingWhite ||
10345              gameMode == IcsPlayingBlack ||
10346              gameMode == BeginningOfGame)) {
10347             SendToProgram("force\n", &first);
10348             if (first.usePing) {
10349               char buf[MSG_SIZ];
10350               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10351               SendToProgram(buf, &first);
10352             }
10353         }
10354     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10355         /* Kill off first chess program */
10356         if (first.isr != NULL)
10357           RemoveInputSource(first.isr);
10358         first.isr = NULL;
10359
10360         if (first.pr != NoProc) {
10361             ExitAnalyzeMode();
10362             DoSleep( appData.delayBeforeQuit );
10363             SendToProgram("quit\n", &first);
10364             DoSleep( appData.delayAfterQuit );
10365             DestroyChildProcess(first.pr, first.useSigterm);
10366         }
10367         first.pr = NoProc;
10368     }
10369     if (second.reuse) {
10370         /* Put second chess program into idle state */
10371         if (second.pr != NoProc &&
10372             gameMode == TwoMachinesPlay) {
10373             SendToProgram("force\n", &second);
10374             if (second.usePing) {
10375               char buf[MSG_SIZ];
10376               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10377               SendToProgram(buf, &second);
10378             }
10379         }
10380     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10381         /* Kill off second chess program */
10382         if (second.isr != NULL)
10383           RemoveInputSource(second.isr);
10384         second.isr = NULL;
10385
10386         if (second.pr != NoProc) {
10387             DoSleep( appData.delayBeforeQuit );
10388             SendToProgram("quit\n", &second);
10389             DoSleep( appData.delayAfterQuit );
10390             DestroyChildProcess(second.pr, second.useSigterm);
10391         }
10392         second.pr = NoProc;
10393     }
10394
10395     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10396         char resChar = '=';
10397         switch (result) {
10398         case WhiteWins:
10399           resChar = '+';
10400           if (first.twoMachinesColor[0] == 'w') {
10401             first.matchWins++;
10402           } else {
10403             second.matchWins++;
10404           }
10405           break;
10406         case BlackWins:
10407           resChar = '-';
10408           if (first.twoMachinesColor[0] == 'b') {
10409             first.matchWins++;
10410           } else {
10411             second.matchWins++;
10412           }
10413           break;
10414         case GameUnfinished:
10415           resChar = ' ';
10416         default:
10417           break;
10418         }
10419
10420         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10421         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10422             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10423             ReserveGame(nextGame, resChar); // sets nextGame
10424             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10425             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10426         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10427
10428         if (nextGame <= appData.matchGames && !abortMatch) {
10429             gameMode = nextGameMode;
10430             matchGame = nextGame; // this will be overruled in tourney mode!
10431             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10432             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10433             endingGame = 0; /* [HGM] crash */
10434             return;
10435         } else {
10436             gameMode = nextGameMode;
10437             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10438                      first.tidy, second.tidy,
10439                      first.matchWins, second.matchWins,
10440                      appData.matchGames - (first.matchWins + second.matchWins));
10441             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10442             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10443             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10444             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10445                 first.twoMachinesColor = "black\n";
10446                 second.twoMachinesColor = "white\n";
10447             } else {
10448                 first.twoMachinesColor = "white\n";
10449                 second.twoMachinesColor = "black\n";
10450             }
10451         }
10452     }
10453     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10454         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10455       ExitAnalyzeMode();
10456     gameMode = nextGameMode;
10457     ModeHighlight();
10458     endingGame = 0;  /* [HGM] crash */
10459     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10460         if(matchMode == TRUE) { // match through command line: exit with or without popup
10461             if(ranking) {
10462                 ToNrEvent(forwardMostMove);
10463                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10464                 else ExitEvent(0);
10465             } else DisplayFatalError(buf, 0, 0);
10466         } else { // match through menu; just stop, with or without popup
10467             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10468             ModeHighlight();
10469             if(ranking){
10470                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10471             } else DisplayNote(buf);
10472       }
10473       if(ranking) free(ranking);
10474     }
10475 }
10476
10477 /* Assumes program was just initialized (initString sent).
10478    Leaves program in force mode. */
10479 void
10480 FeedMovesToProgram (ChessProgramState *cps, int upto)
10481 {
10482     int i;
10483
10484     if (appData.debugMode)
10485       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10486               startedFromSetupPosition ? "position and " : "",
10487               backwardMostMove, upto, cps->which);
10488     if(currentlyInitializedVariant != gameInfo.variant) {
10489       char buf[MSG_SIZ];
10490         // [HGM] variantswitch: make engine aware of new variant
10491         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10492                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10493         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10494         SendToProgram(buf, cps);
10495         currentlyInitializedVariant = gameInfo.variant;
10496     }
10497     SendToProgram("force\n", cps);
10498     if (startedFromSetupPosition) {
10499         SendBoard(cps, backwardMostMove);
10500     if (appData.debugMode) {
10501         fprintf(debugFP, "feedMoves\n");
10502     }
10503     }
10504     for (i = backwardMostMove; i < upto; i++) {
10505         SendMoveToProgram(i, cps);
10506     }
10507 }
10508
10509
10510 int
10511 ResurrectChessProgram ()
10512 {
10513      /* The chess program may have exited.
10514         If so, restart it and feed it all the moves made so far. */
10515     static int doInit = 0;
10516
10517     if (appData.noChessProgram) return 1;
10518
10519     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10520         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10521         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10522         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10523     } else {
10524         if (first.pr != NoProc) return 1;
10525         StartChessProgram(&first);
10526     }
10527     InitChessProgram(&first, FALSE);
10528     FeedMovesToProgram(&first, currentMove);
10529
10530     if (!first.sendTime) {
10531         /* can't tell gnuchess what its clock should read,
10532            so we bow to its notion. */
10533         ResetClocks();
10534         timeRemaining[0][currentMove] = whiteTimeRemaining;
10535         timeRemaining[1][currentMove] = blackTimeRemaining;
10536     }
10537
10538     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10539                 appData.icsEngineAnalyze) && first.analysisSupport) {
10540       SendToProgram("analyze\n", &first);
10541       first.analyzing = TRUE;
10542     }
10543     return 1;
10544 }
10545
10546 /*
10547  * Button procedures
10548  */
10549 void
10550 Reset (int redraw, int init)
10551 {
10552     int i;
10553
10554     if (appData.debugMode) {
10555         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10556                 redraw, init, gameMode);
10557     }
10558     CleanupTail(); // [HGM] vari: delete any stored variations
10559     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10560     pausing = pauseExamInvalid = FALSE;
10561     startedFromSetupPosition = blackPlaysFirst = FALSE;
10562     firstMove = TRUE;
10563     whiteFlag = blackFlag = FALSE;
10564     userOfferedDraw = FALSE;
10565     hintRequested = bookRequested = FALSE;
10566     first.maybeThinking = FALSE;
10567     second.maybeThinking = FALSE;
10568     first.bookSuspend = FALSE; // [HGM] book
10569     second.bookSuspend = FALSE;
10570     thinkOutput[0] = NULLCHAR;
10571     lastHint[0] = NULLCHAR;
10572     ClearGameInfo(&gameInfo);
10573     gameInfo.variant = StringToVariant(appData.variant);
10574     ics_user_moved = ics_clock_paused = FALSE;
10575     ics_getting_history = H_FALSE;
10576     ics_gamenum = -1;
10577     white_holding[0] = black_holding[0] = NULLCHAR;
10578     ClearProgramStats();
10579     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10580
10581     ResetFrontEnd();
10582     ClearHighlights();
10583     flipView = appData.flipView;
10584     ClearPremoveHighlights();
10585     gotPremove = FALSE;
10586     alarmSounded = FALSE;
10587
10588     GameEnds(EndOfFile, NULL, GE_PLAYER);
10589     if(appData.serverMovesName != NULL) {
10590         /* [HGM] prepare to make moves file for broadcasting */
10591         clock_t t = clock();
10592         if(serverMoves != NULL) fclose(serverMoves);
10593         serverMoves = fopen(appData.serverMovesName, "r");
10594         if(serverMoves != NULL) {
10595             fclose(serverMoves);
10596             /* delay 15 sec before overwriting, so all clients can see end */
10597             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10598         }
10599         serverMoves = fopen(appData.serverMovesName, "w");
10600     }
10601
10602     ExitAnalyzeMode();
10603     gameMode = BeginningOfGame;
10604     ModeHighlight();
10605     if(appData.icsActive) gameInfo.variant = VariantNormal;
10606     currentMove = forwardMostMove = backwardMostMove = 0;
10607     MarkTargetSquares(1);
10608     InitPosition(redraw);
10609     for (i = 0; i < MAX_MOVES; i++) {
10610         if (commentList[i] != NULL) {
10611             free(commentList[i]);
10612             commentList[i] = NULL;
10613         }
10614     }
10615     ResetClocks();
10616     timeRemaining[0][0] = whiteTimeRemaining;
10617     timeRemaining[1][0] = blackTimeRemaining;
10618
10619     if (first.pr == NoProc) {
10620         StartChessProgram(&first);
10621     }
10622     if (init) {
10623             InitChessProgram(&first, startedFromSetupPosition);
10624     }
10625     DisplayTitle("");
10626     DisplayMessage("", "");
10627     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10628     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10629 }
10630
10631 void
10632 AutoPlayGameLoop ()
10633 {
10634     for (;;) {
10635         if (!AutoPlayOneMove())
10636           return;
10637         if (matchMode || appData.timeDelay == 0)
10638           continue;
10639         if (appData.timeDelay < 0)
10640           return;
10641         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10642         break;
10643     }
10644 }
10645
10646
10647 int
10648 AutoPlayOneMove ()
10649 {
10650     int fromX, fromY, toX, toY;
10651
10652     if (appData.debugMode) {
10653       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10654     }
10655
10656     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10657       return FALSE;
10658
10659     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10660       pvInfoList[currentMove].depth = programStats.depth;
10661       pvInfoList[currentMove].score = programStats.score;
10662       pvInfoList[currentMove].time  = 0;
10663       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10664     }
10665
10666     if (currentMove >= forwardMostMove) {
10667       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10668 //      gameMode = EndOfGame;
10669 //      ModeHighlight();
10670
10671       /* [AS] Clear current move marker at the end of a game */
10672       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10673
10674       return FALSE;
10675     }
10676
10677     toX = moveList[currentMove][2] - AAA;
10678     toY = moveList[currentMove][3] - ONE;
10679
10680     if (moveList[currentMove][1] == '@') {
10681         if (appData.highlightLastMove) {
10682             SetHighlights(-1, -1, toX, toY);
10683         }
10684     } else {
10685         fromX = moveList[currentMove][0] - AAA;
10686         fromY = moveList[currentMove][1] - ONE;
10687
10688         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10689
10690         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10691
10692         if (appData.highlightLastMove) {
10693             SetHighlights(fromX, fromY, toX, toY);
10694         }
10695     }
10696     DisplayMove(currentMove);
10697     SendMoveToProgram(currentMove++, &first);
10698     DisplayBothClocks();
10699     DrawPosition(FALSE, boards[currentMove]);
10700     // [HGM] PV info: always display, routine tests if empty
10701     DisplayComment(currentMove - 1, commentList[currentMove]);
10702     return TRUE;
10703 }
10704
10705
10706 int
10707 LoadGameOneMove (ChessMove readAhead)
10708 {
10709     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10710     char promoChar = NULLCHAR;
10711     ChessMove moveType;
10712     char move[MSG_SIZ];
10713     char *p, *q;
10714
10715     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10716         gameMode != AnalyzeMode && gameMode != Training) {
10717         gameFileFP = NULL;
10718         return FALSE;
10719     }
10720
10721     yyboardindex = forwardMostMove;
10722     if (readAhead != EndOfFile) {
10723       moveType = readAhead;
10724     } else {
10725       if (gameFileFP == NULL)
10726           return FALSE;
10727       moveType = (ChessMove) Myylex();
10728     }
10729
10730     done = FALSE;
10731     switch (moveType) {
10732       case Comment:
10733         if (appData.debugMode)
10734           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10735         p = yy_text;
10736
10737         /* append the comment but don't display it */
10738         AppendComment(currentMove, p, FALSE);
10739         return TRUE;
10740
10741       case WhiteCapturesEnPassant:
10742       case BlackCapturesEnPassant:
10743       case WhitePromotion:
10744       case BlackPromotion:
10745       case WhiteNonPromotion:
10746       case BlackNonPromotion:
10747       case NormalMove:
10748       case WhiteKingSideCastle:
10749       case WhiteQueenSideCastle:
10750       case BlackKingSideCastle:
10751       case BlackQueenSideCastle:
10752       case WhiteKingSideCastleWild:
10753       case WhiteQueenSideCastleWild:
10754       case BlackKingSideCastleWild:
10755       case BlackQueenSideCastleWild:
10756       /* PUSH Fabien */
10757       case WhiteHSideCastleFR:
10758       case WhiteASideCastleFR:
10759       case BlackHSideCastleFR:
10760       case BlackASideCastleFR:
10761       /* POP Fabien */
10762         if (appData.debugMode)
10763           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10764         fromX = currentMoveString[0] - AAA;
10765         fromY = currentMoveString[1] - ONE;
10766         toX = currentMoveString[2] - AAA;
10767         toY = currentMoveString[3] - ONE;
10768         promoChar = currentMoveString[4];
10769         break;
10770
10771       case WhiteDrop:
10772       case BlackDrop:
10773         if (appData.debugMode)
10774           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10775         fromX = moveType == WhiteDrop ?
10776           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10777         (int) CharToPiece(ToLower(currentMoveString[0]));
10778         fromY = DROP_RANK;
10779         toX = currentMoveString[2] - AAA;
10780         toY = currentMoveString[3] - ONE;
10781         break;
10782
10783       case WhiteWins:
10784       case BlackWins:
10785       case GameIsDrawn:
10786       case GameUnfinished:
10787         if (appData.debugMode)
10788           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10789         p = strchr(yy_text, '{');
10790         if (p == NULL) p = strchr(yy_text, '(');
10791         if (p == NULL) {
10792             p = yy_text;
10793             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10794         } else {
10795             q = strchr(p, *p == '{' ? '}' : ')');
10796             if (q != NULL) *q = NULLCHAR;
10797             p++;
10798         }
10799         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10800         GameEnds(moveType, p, GE_FILE);
10801         done = TRUE;
10802         if (cmailMsgLoaded) {
10803             ClearHighlights();
10804             flipView = WhiteOnMove(currentMove);
10805             if (moveType == GameUnfinished) flipView = !flipView;
10806             if (appData.debugMode)
10807               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10808         }
10809         break;
10810
10811       case EndOfFile:
10812         if (appData.debugMode)
10813           fprintf(debugFP, "Parser hit end of file\n");
10814         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10815           case MT_NONE:
10816           case MT_CHECK:
10817             break;
10818           case MT_CHECKMATE:
10819           case MT_STAINMATE:
10820             if (WhiteOnMove(currentMove)) {
10821                 GameEnds(BlackWins, "Black mates", GE_FILE);
10822             } else {
10823                 GameEnds(WhiteWins, "White mates", GE_FILE);
10824             }
10825             break;
10826           case MT_STALEMATE:
10827             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10828             break;
10829         }
10830         done = TRUE;
10831         break;
10832
10833       case MoveNumberOne:
10834         if (lastLoadGameStart == GNUChessGame) {
10835             /* GNUChessGames have numbers, but they aren't move numbers */
10836             if (appData.debugMode)
10837               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10838                       yy_text, (int) moveType);
10839             return LoadGameOneMove(EndOfFile); /* tail recursion */
10840         }
10841         /* else fall thru */
10842
10843       case XBoardGame:
10844       case GNUChessGame:
10845       case PGNTag:
10846         /* Reached start of next game in file */
10847         if (appData.debugMode)
10848           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10849         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10850           case MT_NONE:
10851           case MT_CHECK:
10852             break;
10853           case MT_CHECKMATE:
10854           case MT_STAINMATE:
10855             if (WhiteOnMove(currentMove)) {
10856                 GameEnds(BlackWins, "Black mates", GE_FILE);
10857             } else {
10858                 GameEnds(WhiteWins, "White mates", GE_FILE);
10859             }
10860             break;
10861           case MT_STALEMATE:
10862             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10863             break;
10864         }
10865         done = TRUE;
10866         break;
10867
10868       case PositionDiagram:     /* should not happen; ignore */
10869       case ElapsedTime:         /* ignore */
10870       case NAG:                 /* ignore */
10871         if (appData.debugMode)
10872           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10873                   yy_text, (int) moveType);
10874         return LoadGameOneMove(EndOfFile); /* tail recursion */
10875
10876       case IllegalMove:
10877         if (appData.testLegality) {
10878             if (appData.debugMode)
10879               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10880             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10881                     (forwardMostMove / 2) + 1,
10882                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10883             DisplayError(move, 0);
10884             done = TRUE;
10885         } else {
10886             if (appData.debugMode)
10887               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10888                       yy_text, currentMoveString);
10889             fromX = currentMoveString[0] - AAA;
10890             fromY = currentMoveString[1] - ONE;
10891             toX = currentMoveString[2] - AAA;
10892             toY = currentMoveString[3] - ONE;
10893             promoChar = currentMoveString[4];
10894         }
10895         break;
10896
10897       case AmbiguousMove:
10898         if (appData.debugMode)
10899           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10900         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10901                 (forwardMostMove / 2) + 1,
10902                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10903         DisplayError(move, 0);
10904         done = TRUE;
10905         break;
10906
10907       default:
10908       case ImpossibleMove:
10909         if (appData.debugMode)
10910           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10911         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10912                 (forwardMostMove / 2) + 1,
10913                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10914         DisplayError(move, 0);
10915         done = TRUE;
10916         break;
10917     }
10918
10919     if (done) {
10920         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10921             DrawPosition(FALSE, boards[currentMove]);
10922             DisplayBothClocks();
10923             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10924               DisplayComment(currentMove - 1, commentList[currentMove]);
10925         }
10926         (void) StopLoadGameTimer();
10927         gameFileFP = NULL;
10928         cmailOldMove = forwardMostMove;
10929         return FALSE;
10930     } else {
10931         /* currentMoveString is set as a side-effect of yylex */
10932
10933         thinkOutput[0] = NULLCHAR;
10934         MakeMove(fromX, fromY, toX, toY, promoChar);
10935         currentMove = forwardMostMove;
10936         return TRUE;
10937     }
10938 }
10939
10940 /* Load the nth game from the given file */
10941 int
10942 LoadGameFromFile (char *filename, int n, char *title, int useList)
10943 {
10944     FILE *f;
10945     char buf[MSG_SIZ];
10946
10947     if (strcmp(filename, "-") == 0) {
10948         f = stdin;
10949         title = "stdin";
10950     } else {
10951         f = fopen(filename, "rb");
10952         if (f == NULL) {
10953           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10954             DisplayError(buf, errno);
10955             return FALSE;
10956         }
10957     }
10958     if (fseek(f, 0, 0) == -1) {
10959         /* f is not seekable; probably a pipe */
10960         useList = FALSE;
10961     }
10962     if (useList && n == 0) {
10963         int error = GameListBuild(f);
10964         if (error) {
10965             DisplayError(_("Cannot build game list"), error);
10966         } else if (!ListEmpty(&gameList) &&
10967                    ((ListGame *) gameList.tailPred)->number > 1) {
10968             GameListPopUp(f, title);
10969             return TRUE;
10970         }
10971         GameListDestroy();
10972         n = 1;
10973     }
10974     if (n == 0) n = 1;
10975     return LoadGame(f, n, title, FALSE);
10976 }
10977
10978
10979 void
10980 MakeRegisteredMove ()
10981 {
10982     int fromX, fromY, toX, toY;
10983     char promoChar;
10984     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10985         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10986           case CMAIL_MOVE:
10987           case CMAIL_DRAW:
10988             if (appData.debugMode)
10989               fprintf(debugFP, "Restoring %s for game %d\n",
10990                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10991
10992             thinkOutput[0] = NULLCHAR;
10993             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10994             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10995             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10996             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10997             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10998             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10999             MakeMove(fromX, fromY, toX, toY, promoChar);
11000             ShowMove(fromX, fromY, toX, toY);
11001
11002             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11003               case MT_NONE:
11004               case MT_CHECK:
11005                 break;
11006
11007               case MT_CHECKMATE:
11008               case MT_STAINMATE:
11009                 if (WhiteOnMove(currentMove)) {
11010                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11011                 } else {
11012                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11013                 }
11014                 break;
11015
11016               case MT_STALEMATE:
11017                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11018                 break;
11019             }
11020
11021             break;
11022
11023           case CMAIL_RESIGN:
11024             if (WhiteOnMove(currentMove)) {
11025                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11026             } else {
11027                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11028             }
11029             break;
11030
11031           case CMAIL_ACCEPT:
11032             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11033             break;
11034
11035           default:
11036             break;
11037         }
11038     }
11039
11040     return;
11041 }
11042
11043 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11044 int
11045 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11046 {
11047     int retVal;
11048
11049     if (gameNumber > nCmailGames) {
11050         DisplayError(_("No more games in this message"), 0);
11051         return FALSE;
11052     }
11053     if (f == lastLoadGameFP) {
11054         int offset = gameNumber - lastLoadGameNumber;
11055         if (offset == 0) {
11056             cmailMsg[0] = NULLCHAR;
11057             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11058                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11059                 nCmailMovesRegistered--;
11060             }
11061             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11062             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11063                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11064             }
11065         } else {
11066             if (! RegisterMove()) return FALSE;
11067         }
11068     }
11069
11070     retVal = LoadGame(f, gameNumber, title, useList);
11071
11072     /* Make move registered during previous look at this game, if any */
11073     MakeRegisteredMove();
11074
11075     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11076         commentList[currentMove]
11077           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11078         DisplayComment(currentMove - 1, commentList[currentMove]);
11079     }
11080
11081     return retVal;
11082 }
11083
11084 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11085 int
11086 ReloadGame (int offset)
11087 {
11088     int gameNumber = lastLoadGameNumber + offset;
11089     if (lastLoadGameFP == NULL) {
11090         DisplayError(_("No game has been loaded yet"), 0);
11091         return FALSE;
11092     }
11093     if (gameNumber <= 0) {
11094         DisplayError(_("Can't back up any further"), 0);
11095         return FALSE;
11096     }
11097     if (cmailMsgLoaded) {
11098         return CmailLoadGame(lastLoadGameFP, gameNumber,
11099                              lastLoadGameTitle, lastLoadGameUseList);
11100     } else {
11101         return LoadGame(lastLoadGameFP, gameNumber,
11102                         lastLoadGameTitle, lastLoadGameUseList);
11103     }
11104 }
11105
11106 int keys[EmptySquare+1];
11107
11108 int
11109 PositionMatches (Board b1, Board b2)
11110 {
11111     int r, f, sum=0;
11112     switch(appData.searchMode) {
11113         case 1: return CompareWithRights(b1, b2);
11114         case 2:
11115             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11116                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11117             }
11118             return TRUE;
11119         case 3:
11120             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11121               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11122                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11123             }
11124             return sum==0;
11125         case 4:
11126             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11127                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11128             }
11129             return sum==0;
11130     }
11131     return TRUE;
11132 }
11133
11134 #define Q_PROMO  4
11135 #define Q_EP     3
11136 #define Q_BCASTL 2
11137 #define Q_WCASTL 1
11138
11139 int pieceList[256], quickBoard[256];
11140 ChessSquare pieceType[256] = { EmptySquare };
11141 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11142 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11143 int soughtTotal, turn;
11144 Boolean epOK, flipSearch;
11145
11146 typedef struct {
11147     unsigned char piece, to;
11148 } Move;
11149
11150 #define DSIZE (250000)
11151
11152 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11153 Move *moveDatabase = initialSpace;
11154 unsigned int movePtr, dataSize = DSIZE;
11155
11156 int
11157 MakePieceList (Board board, int *counts)
11158 {
11159     int r, f, n=Q_PROMO, total=0;
11160     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11161     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11162         int sq = f + (r<<4);
11163         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11164             quickBoard[sq] = ++n;
11165             pieceList[n] = sq;
11166             pieceType[n] = board[r][f];
11167             counts[board[r][f]]++;
11168             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11169             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11170             total++;
11171         }
11172     }
11173     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11174     return total;
11175 }
11176
11177 void
11178 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11179 {
11180     int sq = fromX + (fromY<<4);
11181     int piece = quickBoard[sq];
11182     quickBoard[sq] = 0;
11183     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11184     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11185         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11186         moveDatabase[movePtr++].piece = Q_WCASTL;
11187         quickBoard[sq] = piece;
11188         piece = quickBoard[from]; quickBoard[from] = 0;
11189         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11190     } else
11191     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11192         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11193         moveDatabase[movePtr++].piece = Q_BCASTL;
11194         quickBoard[sq] = piece;
11195         piece = quickBoard[from]; quickBoard[from] = 0;
11196         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11197     } else
11198     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11199         quickBoard[(fromY<<4)+toX] = 0;
11200         moveDatabase[movePtr].piece = Q_EP;
11201         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11202         moveDatabase[movePtr].to = sq;
11203     } else
11204     if(promoPiece != pieceType[piece]) {
11205         moveDatabase[movePtr++].piece = Q_PROMO;
11206         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11207     }
11208     moveDatabase[movePtr].piece = piece;
11209     quickBoard[sq] = piece;
11210     movePtr++;
11211 }
11212
11213 int
11214 PackGame (Board board)
11215 {
11216     Move *newSpace = NULL;
11217     moveDatabase[movePtr].piece = 0; // terminate previous game
11218     if(movePtr > dataSize) {
11219         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11220         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11221         if(dataSize) newSpace = (Move*) calloc(8*dataSize + 1000, sizeof(Move));
11222         if(newSpace) {
11223             int i;
11224             Move *p = moveDatabase, *q = newSpace;
11225             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11226             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11227             moveDatabase = newSpace;
11228         } else { // calloc failed, we must be out of memory. Too bad...
11229             dataSize = 0; // prevent calloc events for all subsequent games
11230             return 0;     // and signal this one isn't cached
11231         }
11232     }
11233     movePtr++;
11234     MakePieceList(board, counts);
11235     return movePtr;
11236 }
11237
11238 int
11239 QuickCompare (Board board, int *minCounts, int *maxCounts)
11240 {   // compare according to search mode
11241     int r, f;
11242     switch(appData.searchMode)
11243     {
11244       case 1: // exact position match
11245         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11246         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11247             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11248         }
11249         break;
11250       case 2: // can have extra material on empty squares
11251         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11252             if(board[r][f] == EmptySquare) continue;
11253             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11254         }
11255         break;
11256       case 3: // material with exact Pawn structure
11257         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11258             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11259             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11260         } // fall through to material comparison
11261       case 4: // exact material
11262         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11263         break;
11264       case 6: // material range with given imbalance
11265         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11266         // fall through to range comparison
11267       case 5: // material range
11268         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11269     }
11270     return TRUE;
11271 }
11272
11273 int
11274 QuickScan (Board board, Move *move)
11275 {   // reconstruct game,and compare all positions in it
11276     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11277     do {
11278         int piece = move->piece;
11279         int to = move->to, from = pieceList[piece];
11280         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11281           if(!piece) return -1;
11282           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11283             piece = (++move)->piece;
11284             from = pieceList[piece];
11285             counts[pieceType[piece]]--;
11286             pieceType[piece] = (ChessSquare) move->to;
11287             counts[move->to]++;
11288           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11289             counts[pieceType[quickBoard[to]]]--;
11290             quickBoard[to] = 0; total--;
11291             move++;
11292             continue;
11293           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11294             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11295             from  = pieceList[piece]; // so this must be King
11296             quickBoard[from] = 0;
11297             quickBoard[to] = piece;
11298             pieceList[piece] = to;
11299             move++;
11300             continue;
11301           }
11302         }
11303         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11304         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11305         quickBoard[from] = 0;
11306         quickBoard[to] = piece;
11307         pieceList[piece] = to;
11308         cnt++; turn ^= 3;
11309         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11310            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11311            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11312                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11313           ) {
11314             static int lastCounts[EmptySquare+1];
11315             int i;
11316             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11317             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11318         } else stretch = 0;
11319         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11320         move++;
11321     } while(1);
11322 }
11323
11324 void
11325 InitSearch ()
11326 {
11327     int r, f;
11328     flipSearch = FALSE;
11329     CopyBoard(soughtBoard, boards[currentMove]);
11330     soughtTotal = MakePieceList(soughtBoard, maxSought);
11331     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11332     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11333     CopyBoard(reverseBoard, boards[currentMove]);
11334     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11335         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11336         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11337         reverseBoard[r][f] = piece;
11338     }
11339     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11340     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11341     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11342                  || (boards[currentMove][CASTLING][2] == NoRights || 
11343                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11344                  && (boards[currentMove][CASTLING][5] == NoRights || 
11345                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11346       ) {
11347         flipSearch = TRUE;
11348         CopyBoard(flipBoard, soughtBoard);
11349         CopyBoard(rotateBoard, reverseBoard);
11350         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11351             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11352             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11353         }
11354     }
11355     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11356     if(appData.searchMode >= 5) {
11357         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11358         MakePieceList(soughtBoard, minSought);
11359         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11360     }
11361     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11362         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11363 }
11364
11365 GameInfo dummyInfo;
11366
11367 int
11368 GameContainsPosition (FILE *f, ListGame *lg)
11369 {
11370     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11371     int fromX, fromY, toX, toY;
11372     char promoChar;
11373     static int initDone=FALSE;
11374
11375     // weed out games based on numerical tag comparison
11376     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11377     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11378     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11379     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11380     if(!initDone) {
11381         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11382         initDone = TRUE;
11383     }
11384     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11385     else CopyBoard(boards[scratch], initialPosition); // default start position
11386     if(lg->moves) {
11387         turn = btm + 1;
11388         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11389         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11390     }
11391     if(btm) plyNr++;
11392     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11393     fseek(f, lg->offset, 0);
11394     yynewfile(f);
11395     while(1) {
11396         yyboardindex = scratch;
11397         quickFlag = plyNr+1;
11398         next = Myylex();
11399         quickFlag = 0;
11400         switch(next) {
11401             case PGNTag:
11402                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11403             default:
11404                 continue;
11405
11406             case XBoardGame:
11407             case GNUChessGame:
11408                 if(plyNr) return -1; // after we have seen moves, this is for new game
11409               continue;
11410
11411             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11412             case ImpossibleMove:
11413             case WhiteWins: // game ends here with these four
11414             case BlackWins:
11415             case GameIsDrawn:
11416             case GameUnfinished:
11417                 return -1;
11418
11419             case IllegalMove:
11420                 if(appData.testLegality) return -1;
11421             case WhiteCapturesEnPassant:
11422             case BlackCapturesEnPassant:
11423             case WhitePromotion:
11424             case BlackPromotion:
11425             case WhiteNonPromotion:
11426             case BlackNonPromotion:
11427             case NormalMove:
11428             case WhiteKingSideCastle:
11429             case WhiteQueenSideCastle:
11430             case BlackKingSideCastle:
11431             case BlackQueenSideCastle:
11432             case WhiteKingSideCastleWild:
11433             case WhiteQueenSideCastleWild:
11434             case BlackKingSideCastleWild:
11435             case BlackQueenSideCastleWild:
11436             case WhiteHSideCastleFR:
11437             case WhiteASideCastleFR:
11438             case BlackHSideCastleFR:
11439             case BlackASideCastleFR:
11440                 fromX = currentMoveString[0] - AAA;
11441                 fromY = currentMoveString[1] - ONE;
11442                 toX = currentMoveString[2] - AAA;
11443                 toY = currentMoveString[3] - ONE;
11444                 promoChar = currentMoveString[4];
11445                 break;
11446             case WhiteDrop:
11447             case BlackDrop:
11448                 fromX = next == WhiteDrop ?
11449                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11450                   (int) CharToPiece(ToLower(currentMoveString[0]));
11451                 fromY = DROP_RANK;
11452                 toX = currentMoveString[2] - AAA;
11453                 toY = currentMoveString[3] - ONE;
11454                 promoChar = 0;
11455                 break;
11456         }
11457         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11458         plyNr++;
11459         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11460         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11461         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11462         if(appData.findMirror) {
11463             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11464             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11465         }
11466     }
11467 }
11468
11469 /* Load the nth game from open file f */
11470 int
11471 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11472 {
11473     ChessMove cm;
11474     char buf[MSG_SIZ];
11475     int gn = gameNumber;
11476     ListGame *lg = NULL;
11477     int numPGNTags = 0;
11478     int err, pos = -1;
11479     GameMode oldGameMode;
11480     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11481
11482     if (appData.debugMode)
11483         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11484
11485     if (gameMode == Training )
11486         SetTrainingModeOff();
11487
11488     oldGameMode = gameMode;
11489     if (gameMode != BeginningOfGame) {
11490       Reset(FALSE, TRUE);
11491     }
11492
11493     gameFileFP = f;
11494     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11495         fclose(lastLoadGameFP);
11496     }
11497
11498     if (useList) {
11499         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11500
11501         if (lg) {
11502             fseek(f, lg->offset, 0);
11503             GameListHighlight(gameNumber);
11504             pos = lg->position;
11505             gn = 1;
11506         }
11507         else {
11508             DisplayError(_("Game number out of range"), 0);
11509             return FALSE;
11510         }
11511     } else {
11512         GameListDestroy();
11513         if (fseek(f, 0, 0) == -1) {
11514             if (f == lastLoadGameFP ?
11515                 gameNumber == lastLoadGameNumber + 1 :
11516                 gameNumber == 1) {
11517                 gn = 1;
11518             } else {
11519                 DisplayError(_("Can't seek on game file"), 0);
11520                 return FALSE;
11521             }
11522         }
11523     }
11524     lastLoadGameFP = f;
11525     lastLoadGameNumber = gameNumber;
11526     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11527     lastLoadGameUseList = useList;
11528
11529     yynewfile(f);
11530
11531     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11532       snprintf(buf, sizeof(buf), _("%s vs. %s"), lg->gameInfo.white,
11533                 lg->gameInfo.black);
11534             DisplayTitle(buf);
11535     } else if (*title != NULLCHAR) {
11536         if (gameNumber > 1) {
11537           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11538             DisplayTitle(buf);
11539         } else {
11540             DisplayTitle(title);
11541         }
11542     }
11543
11544     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11545         gameMode = PlayFromGameFile;
11546         ModeHighlight();
11547     }
11548
11549     currentMove = forwardMostMove = backwardMostMove = 0;
11550     CopyBoard(boards[0], initialPosition);
11551     StopClocks();
11552
11553     /*
11554      * Skip the first gn-1 games in the file.
11555      * Also skip over anything that precedes an identifiable
11556      * start of game marker, to avoid being confused by
11557      * garbage at the start of the file.  Currently
11558      * recognized start of game markers are the move number "1",
11559      * the pattern "gnuchess .* game", the pattern
11560      * "^[#;%] [^ ]* game file", and a PGN tag block.
11561      * A game that starts with one of the latter two patterns
11562      * will also have a move number 1, possibly
11563      * following a position diagram.
11564      * 5-4-02: Let's try being more lenient and allowing a game to
11565      * start with an unnumbered move.  Does that break anything?
11566      */
11567     cm = lastLoadGameStart = EndOfFile;
11568     while (gn > 0) {
11569         yyboardindex = forwardMostMove;
11570         cm = (ChessMove) Myylex();
11571         switch (cm) {
11572           case EndOfFile:
11573             if (cmailMsgLoaded) {
11574                 nCmailGames = CMAIL_MAX_GAMES - gn;
11575             } else {
11576                 Reset(TRUE, TRUE);
11577                 DisplayError(_("Game not found in file"), 0);
11578             }
11579             return FALSE;
11580
11581           case GNUChessGame:
11582           case XBoardGame:
11583             gn--;
11584             lastLoadGameStart = cm;
11585             break;
11586
11587           case MoveNumberOne:
11588             switch (lastLoadGameStart) {
11589               case GNUChessGame:
11590               case XBoardGame:
11591               case PGNTag:
11592                 break;
11593               case MoveNumberOne:
11594               case EndOfFile:
11595                 gn--;           /* count this game */
11596                 lastLoadGameStart = cm;
11597                 break;
11598               default:
11599                 /* impossible */
11600                 break;
11601             }
11602             break;
11603
11604           case PGNTag:
11605             switch (lastLoadGameStart) {
11606               case GNUChessGame:
11607               case PGNTag:
11608               case MoveNumberOne:
11609               case EndOfFile:
11610                 gn--;           /* count this game */
11611                 lastLoadGameStart = cm;
11612                 break;
11613               case XBoardGame:
11614                 lastLoadGameStart = cm; /* game counted already */
11615                 break;
11616               default:
11617                 /* impossible */
11618                 break;
11619             }
11620             if (gn > 0) {
11621                 do {
11622                     yyboardindex = forwardMostMove;
11623                     cm = (ChessMove) Myylex();
11624                 } while (cm == PGNTag || cm == Comment);
11625             }
11626             break;
11627
11628           case WhiteWins:
11629           case BlackWins:
11630           case GameIsDrawn:
11631             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11632                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11633                     != CMAIL_OLD_RESULT) {
11634                     nCmailResults ++ ;
11635                     cmailResult[  CMAIL_MAX_GAMES
11636                                 - gn - 1] = CMAIL_OLD_RESULT;
11637                 }
11638             }
11639             break;
11640
11641           case NormalMove:
11642             /* Only a NormalMove can be at the start of a game
11643              * without a position diagram. */
11644             if (lastLoadGameStart == EndOfFile ) {
11645               gn--;
11646               lastLoadGameStart = MoveNumberOne;
11647             }
11648             break;
11649
11650           default:
11651             break;
11652         }
11653     }
11654
11655     if (appData.debugMode)
11656       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11657
11658     if (cm == XBoardGame) {
11659         /* Skip any header junk before position diagram and/or move 1 */
11660         for (;;) {
11661             yyboardindex = forwardMostMove;
11662             cm = (ChessMove) Myylex();
11663
11664             if (cm == EndOfFile ||
11665                 cm == GNUChessGame || cm == XBoardGame) {
11666                 /* Empty game; pretend end-of-file and handle later */
11667                 cm = EndOfFile;
11668                 break;
11669             }
11670
11671             if (cm == MoveNumberOne || cm == PositionDiagram ||
11672                 cm == PGNTag || cm == Comment)
11673               break;
11674         }
11675     } else if (cm == GNUChessGame) {
11676         if (gameInfo.event != NULL) {
11677             free(gameInfo.event);
11678         }
11679         gameInfo.event = StrSave(yy_text);
11680     }
11681
11682     startedFromSetupPosition = FALSE;
11683     while (cm == PGNTag) {
11684         if (appData.debugMode)
11685           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11686         err = ParsePGNTag(yy_text, &gameInfo);
11687         if (!err) numPGNTags++;
11688
11689         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11690         if(gameInfo.variant != oldVariant) {
11691             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11692             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11693             InitPosition(TRUE);
11694             oldVariant = gameInfo.variant;
11695             if (appData.debugMode)
11696               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11697         }
11698
11699
11700         if (gameInfo.fen != NULL) {
11701           Board initial_position;
11702           startedFromSetupPosition = TRUE;
11703           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11704             Reset(TRUE, TRUE);
11705             DisplayError(_("Bad FEN position in file"), 0);
11706             return FALSE;
11707           }
11708           CopyBoard(boards[0], initial_position);
11709           if (blackPlaysFirst) {
11710             currentMove = forwardMostMove = backwardMostMove = 1;
11711             CopyBoard(boards[1], initial_position);
11712             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11713             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11714             timeRemaining[0][1] = whiteTimeRemaining;
11715             timeRemaining[1][1] = blackTimeRemaining;
11716             if (commentList[0] != NULL) {
11717               commentList[1] = commentList[0];
11718               commentList[0] = NULL;
11719             }
11720           } else {
11721             currentMove = forwardMostMove = backwardMostMove = 0;
11722           }
11723           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11724           {   int i;
11725               initialRulePlies = FENrulePlies;
11726               for( i=0; i< nrCastlingRights; i++ )
11727                   initialRights[i] = initial_position[CASTLING][i];
11728           }
11729           yyboardindex = forwardMostMove;
11730           free(gameInfo.fen);
11731           gameInfo.fen = NULL;
11732         }
11733
11734         yyboardindex = forwardMostMove;
11735         cm = (ChessMove) Myylex();
11736
11737         /* Handle comments interspersed among the tags */
11738         while (cm == Comment) {
11739             char *p;
11740             if (appData.debugMode)
11741               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11742             p = yy_text;
11743             AppendComment(currentMove, p, FALSE);
11744             yyboardindex = forwardMostMove;
11745             cm = (ChessMove) Myylex();
11746         }
11747     }
11748
11749     /* don't rely on existence of Event tag since if game was
11750      * pasted from clipboard the Event tag may not exist
11751      */
11752     if (numPGNTags > 0){
11753         char *tags;
11754         if (gameInfo.variant == VariantNormal) {
11755           VariantClass v = StringToVariant(gameInfo.event);
11756           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11757           if(v < VariantShogi) gameInfo.variant = v;
11758         }
11759         if (!matchMode) {
11760           if( appData.autoDisplayTags ) {
11761             tags = PGNTags(&gameInfo);
11762             TagsPopUp(tags, CmailMsg());
11763             free(tags);
11764           }
11765         }
11766     } else {
11767         /* Make something up, but don't display it now */
11768         SetGameInfo();
11769         TagsPopDown();
11770     }
11771
11772     if (cm == PositionDiagram) {
11773         int i, j;
11774         char *p;
11775         Board initial_position;
11776
11777         if (appData.debugMode)
11778           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11779
11780         if (!startedFromSetupPosition) {
11781             p = yy_text;
11782             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11783               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11784                 switch (*p) {
11785                   case '{':
11786                   case '[':
11787                   case '-':
11788                   case ' ':
11789                   case '\t':
11790                   case '\n':
11791                   case '\r':
11792                     break;
11793                   default:
11794                     initial_position[i][j++] = CharToPiece(*p);
11795                     break;
11796                 }
11797             while (*p == ' ' || *p == '\t' ||
11798                    *p == '\n' || *p == '\r') p++;
11799
11800             if (strncmp(p, "black", strlen("black"))==0)
11801               blackPlaysFirst = TRUE;
11802             else
11803               blackPlaysFirst = FALSE;
11804             startedFromSetupPosition = TRUE;
11805
11806             CopyBoard(boards[0], initial_position);
11807             if (blackPlaysFirst) {
11808                 currentMove = forwardMostMove = backwardMostMove = 1;
11809                 CopyBoard(boards[1], initial_position);
11810                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11811                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11812                 timeRemaining[0][1] = whiteTimeRemaining;
11813                 timeRemaining[1][1] = blackTimeRemaining;
11814                 if (commentList[0] != NULL) {
11815                     commentList[1] = commentList[0];
11816                     commentList[0] = NULL;
11817                 }
11818             } else {
11819                 currentMove = forwardMostMove = backwardMostMove = 0;
11820             }
11821         }
11822         yyboardindex = forwardMostMove;
11823         cm = (ChessMove) Myylex();
11824     }
11825
11826     if (first.pr == NoProc) {
11827         StartChessProgram(&first);
11828     }
11829     InitChessProgram(&first, FALSE);
11830     SendToProgram("force\n", &first);
11831     if (startedFromSetupPosition) {
11832         SendBoard(&first, forwardMostMove);
11833     if (appData.debugMode) {
11834         fprintf(debugFP, "Load Game\n");
11835     }
11836         DisplayBothClocks();
11837     }
11838
11839     /* [HGM] server: flag to write setup moves in broadcast file as one */
11840     loadFlag = appData.suppressLoadMoves;
11841
11842     while (cm == Comment) {
11843         char *p;
11844         if (appData.debugMode)
11845           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11846         p = yy_text;
11847         AppendComment(currentMove, p, FALSE);
11848         yyboardindex = forwardMostMove;
11849         cm = (ChessMove) Myylex();
11850     }
11851
11852     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11853         cm == WhiteWins || cm == BlackWins ||
11854         cm == GameIsDrawn || cm == GameUnfinished) {
11855         DisplayMessage("", _("No moves in game"));
11856         if (cmailMsgLoaded) {
11857             if (appData.debugMode)
11858               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11859             ClearHighlights();
11860             flipView = FALSE;
11861         }
11862         DrawPosition(FALSE, boards[currentMove]);
11863         DisplayBothClocks();
11864         gameMode = EditGame;
11865         ModeHighlight();
11866         gameFileFP = NULL;
11867         cmailOldMove = 0;
11868         return TRUE;
11869     }
11870
11871     // [HGM] PV info: routine tests if comment empty
11872     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11873         DisplayComment(currentMove - 1, commentList[currentMove]);
11874     }
11875     if (!matchMode && appData.timeDelay != 0)
11876       DrawPosition(FALSE, boards[currentMove]);
11877
11878     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11879       programStats.ok_to_send = 1;
11880     }
11881
11882     /* if the first token after the PGN tags is a move
11883      * and not move number 1, retrieve it from the parser
11884      */
11885     if (cm != MoveNumberOne)
11886         LoadGameOneMove(cm);
11887
11888     /* load the remaining moves from the file */
11889     while (LoadGameOneMove(EndOfFile)) {
11890       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11891       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11892     }
11893
11894     /* rewind to the start of the game */
11895     currentMove = backwardMostMove;
11896
11897     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11898
11899     if (oldGameMode == AnalyzeFile ||
11900         oldGameMode == AnalyzeMode) {
11901       AnalyzeFileEvent();
11902     }
11903
11904     if (!matchMode && pos >= 0) {
11905         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11906     } else
11907     if (matchMode || appData.timeDelay == 0) {
11908       ToEndEvent();
11909     } else if (appData.timeDelay > 0) {
11910       AutoPlayGameLoop();
11911     }
11912
11913     if (appData.debugMode)
11914         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11915
11916     loadFlag = 0; /* [HGM] true game starts */
11917     return TRUE;
11918 }
11919
11920 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11921 int
11922 ReloadPosition (int offset)
11923 {
11924     int positionNumber = lastLoadPositionNumber + offset;
11925     if (lastLoadPositionFP == NULL) {
11926         DisplayError(_("No position has been loaded yet"), 0);
11927         return FALSE;
11928     }
11929     if (positionNumber <= 0) {
11930         DisplayError(_("Can't back up any further"), 0);
11931         return FALSE;
11932     }
11933     return LoadPosition(lastLoadPositionFP, positionNumber,
11934                         lastLoadPositionTitle);
11935 }
11936
11937 /* Load the nth position from the given file */
11938 int
11939 LoadPositionFromFile (char *filename, int n, char *title)
11940 {
11941     FILE *f;
11942     char buf[MSG_SIZ];
11943
11944     if (strcmp(filename, "-") == 0) {
11945         return LoadPosition(stdin, n, "stdin");
11946     } else {
11947         f = fopen(filename, "rb");
11948         if (f == NULL) {
11949             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11950             DisplayError(buf, errno);
11951             return FALSE;
11952         } else {
11953             return LoadPosition(f, n, title);
11954         }
11955     }
11956 }
11957
11958 /* Load the nth position from the given open file, and close it */
11959 int
11960 LoadPosition (FILE *f, int positionNumber, char *title)
11961 {
11962     char *p, line[MSG_SIZ];
11963     Board initial_position;
11964     int i, j, fenMode, pn;
11965
11966     if (gameMode == Training )
11967         SetTrainingModeOff();
11968
11969     if (gameMode != BeginningOfGame) {
11970         Reset(FALSE, TRUE);
11971     }
11972     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11973         fclose(lastLoadPositionFP);
11974     }
11975     if (positionNumber == 0) positionNumber = 1;
11976     lastLoadPositionFP = f;
11977     lastLoadPositionNumber = positionNumber;
11978     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11979     if (first.pr == NoProc && !appData.noChessProgram) {
11980       StartChessProgram(&first);
11981       InitChessProgram(&first, FALSE);
11982     }
11983     pn = positionNumber;
11984     if (positionNumber < 0) {
11985         /* Negative position number means to seek to that byte offset */
11986         if (fseek(f, -positionNumber, 0) == -1) {
11987             DisplayError(_("Can't seek on position file"), 0);
11988             return FALSE;
11989         };
11990         pn = 1;
11991     } else {
11992         if (fseek(f, 0, 0) == -1) {
11993             if (f == lastLoadPositionFP ?
11994                 positionNumber == lastLoadPositionNumber + 1 :
11995                 positionNumber == 1) {
11996                 pn = 1;
11997             } else {
11998                 DisplayError(_("Can't seek on position file"), 0);
11999                 return FALSE;
12000             }
12001         }
12002     }
12003     /* See if this file is FEN or old-style xboard */
12004     if (fgets(line, MSG_SIZ, f) == NULL) {
12005         DisplayError(_("Position not found in file"), 0);
12006         return FALSE;
12007     }
12008     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12009     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12010
12011     if (pn >= 2) {
12012         if (fenMode || line[0] == '#') pn--;
12013         while (pn > 0) {
12014             /* skip positions before number pn */
12015             if (fgets(line, MSG_SIZ, f) == NULL) {
12016                 Reset(TRUE, TRUE);
12017                 DisplayError(_("Position not found in file"), 0);
12018                 return FALSE;
12019             }
12020             if (fenMode || line[0] == '#') pn--;
12021         }
12022     }
12023
12024     if (fenMode) {
12025         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12026             DisplayError(_("Bad FEN position in file"), 0);
12027             return FALSE;
12028         }
12029     } else {
12030         (void) fgets(line, MSG_SIZ, f);
12031         (void) fgets(line, MSG_SIZ, f);
12032
12033         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12034             (void) fgets(line, MSG_SIZ, f);
12035             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12036                 if (*p == ' ')
12037                   continue;
12038                 initial_position[i][j++] = CharToPiece(*p);
12039             }
12040         }
12041
12042         blackPlaysFirst = FALSE;
12043         if (!feof(f)) {
12044             (void) fgets(line, MSG_SIZ, f);
12045             if (strncmp(line, "black", strlen("black"))==0)
12046               blackPlaysFirst = TRUE;
12047         }
12048     }
12049     startedFromSetupPosition = TRUE;
12050
12051     CopyBoard(boards[0], initial_position);
12052     if (blackPlaysFirst) {
12053         currentMove = forwardMostMove = backwardMostMove = 1;
12054         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12055         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12056         CopyBoard(boards[1], initial_position);
12057         DisplayMessage("", _("Black to play"));
12058     } else {
12059         currentMove = forwardMostMove = backwardMostMove = 0;
12060         DisplayMessage("", _("White to play"));
12061     }
12062     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12063     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12064         SendToProgram("force\n", &first);
12065         SendBoard(&first, forwardMostMove);
12066     }
12067     if (appData.debugMode) {
12068 int i, j;
12069   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12070   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12071         fprintf(debugFP, "Load Position\n");
12072     }
12073
12074     if (positionNumber > 1) {
12075       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12076         DisplayTitle(line);
12077     } else {
12078         DisplayTitle(title);
12079     }
12080     gameMode = EditGame;
12081     ModeHighlight();
12082     ResetClocks();
12083     timeRemaining[0][1] = whiteTimeRemaining;
12084     timeRemaining[1][1] = blackTimeRemaining;
12085     DrawPosition(FALSE, boards[currentMove]);
12086
12087     return TRUE;
12088 }
12089
12090
12091 void
12092 CopyPlayerNameIntoFileName (char **dest, char *src)
12093 {
12094     while (*src != NULLCHAR && *src != ',') {
12095         if (*src == ' ') {
12096             *(*dest)++ = '_';
12097             src++;
12098         } else {
12099             *(*dest)++ = *src++;
12100         }
12101     }
12102 }
12103
12104 char *
12105 DefaultFileName (char *ext)
12106 {
12107     static char def[MSG_SIZ];
12108     char *p;
12109
12110     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12111         p = def;
12112         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12113         *p++ = '-';
12114         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12115         *p++ = '.';
12116         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12117     } else {
12118         def[0] = NULLCHAR;
12119     }
12120     return def;
12121 }
12122
12123 /* Save the current game to the given file */
12124 int
12125 SaveGameToFile (char *filename, int append)
12126 {
12127     FILE *f;
12128     char buf[MSG_SIZ];
12129     int result, i, t,tot=0;
12130
12131     if (strcmp(filename, "-") == 0) {
12132         return SaveGame(stdout, 0, NULL);
12133     } else {
12134         for(i=0; i<10; i++) { // upto 10 tries
12135              f = fopen(filename, append ? "a" : "w");
12136              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12137              if(f || errno != 13) break;
12138              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12139              tot += t;
12140         }
12141         if (f == NULL) {
12142             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12143             DisplayError(buf, errno);
12144             return FALSE;
12145         } else {
12146             safeStrCpy(buf, lastMsg, MSG_SIZ);
12147             DisplayMessage(_("Waiting for access to save file"), "");
12148             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12149             DisplayMessage(_("Saving game"), "");
12150             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12151             result = SaveGame(f, 0, NULL);
12152             DisplayMessage(buf, "");
12153             return result;
12154         }
12155     }
12156 }
12157
12158 char *
12159 SavePart (char *str)
12160 {
12161     static char buf[MSG_SIZ];
12162     char *p;
12163
12164     p = strchr(str, ' ');
12165     if (p == NULL) return str;
12166     strncpy(buf, str, p - str);
12167     buf[p - str] = NULLCHAR;
12168     return buf;
12169 }
12170
12171 #define PGN_MAX_LINE 75
12172
12173 #define PGN_SIDE_WHITE  0
12174 #define PGN_SIDE_BLACK  1
12175
12176 static int
12177 FindFirstMoveOutOfBook (int side)
12178 {
12179     int result = -1;
12180
12181     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12182         int index = backwardMostMove;
12183         int has_book_hit = 0;
12184
12185         if( (index % 2) != side ) {
12186             index++;
12187         }
12188
12189         while( index < forwardMostMove ) {
12190             /* Check to see if engine is in book */
12191             int depth = pvInfoList[index].depth;
12192             int score = pvInfoList[index].score;
12193             int in_book = 0;
12194
12195             if( depth <= 2 ) {
12196                 in_book = 1;
12197             }
12198             else if( score == 0 && depth == 63 ) {
12199                 in_book = 1; /* Zappa */
12200             }
12201             else if( score == 2 && depth == 99 ) {
12202                 in_book = 1; /* Abrok */
12203             }
12204
12205             has_book_hit += in_book;
12206
12207             if( ! in_book ) {
12208                 result = index;
12209
12210                 break;
12211             }
12212
12213             index += 2;
12214         }
12215     }
12216
12217     return result;
12218 }
12219
12220 void
12221 GetOutOfBookInfo (char * buf)
12222 {
12223     int oob[2];
12224     int i;
12225     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12226
12227     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12228     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12229
12230     *buf = '\0';
12231
12232     if( oob[0] >= 0 || oob[1] >= 0 ) {
12233         for( i=0; i<2; i++ ) {
12234             int idx = oob[i];
12235
12236             if( idx >= 0 ) {
12237                 if( i > 0 && oob[0] >= 0 ) {
12238                     strcat( buf, "   " );
12239                 }
12240
12241                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12242                 sprintf( buf+strlen(buf), "%s%.2f",
12243                     pvInfoList[idx].score >= 0 ? "+" : "",
12244                     pvInfoList[idx].score / 100.0 );
12245             }
12246         }
12247     }
12248 }
12249
12250 /* Save game in PGN style and close the file */
12251 int
12252 SaveGamePGN (FILE *f)
12253 {
12254     int i, offset, linelen, newblock;
12255     time_t tm;
12256 //    char *movetext;
12257     char numtext[32];
12258     int movelen, numlen, blank;
12259     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12260
12261     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12262
12263     tm = time((time_t *) NULL);
12264
12265     PrintPGNTags(f, &gameInfo);
12266
12267     if (backwardMostMove > 0 || startedFromSetupPosition) {
12268         char *fen = PositionToFEN(backwardMostMove, NULL);
12269         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12270         fprintf(f, "\n{--------------\n");
12271         PrintPosition(f, backwardMostMove);
12272         fprintf(f, "--------------}\n");
12273         free(fen);
12274     }
12275     else {
12276         /* [AS] Out of book annotation */
12277         if( appData.saveOutOfBookInfo ) {
12278             char buf[64];
12279
12280             GetOutOfBookInfo( buf );
12281
12282             if( buf[0] != '\0' ) {
12283                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12284             }
12285         }
12286
12287         fprintf(f, "\n");
12288     }
12289
12290     i = backwardMostMove;
12291     linelen = 0;
12292     newblock = TRUE;
12293
12294     while (i < forwardMostMove) {
12295         /* Print comments preceding this move */
12296         if (commentList[i] != NULL) {
12297             if (linelen > 0) fprintf(f, "\n");
12298             fprintf(f, "%s", commentList[i]);
12299             linelen = 0;
12300             newblock = TRUE;
12301         }
12302
12303         /* Format move number */
12304         if ((i % 2) == 0)
12305           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12306         else
12307           if (newblock)
12308             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12309           else
12310             numtext[0] = NULLCHAR;
12311
12312         numlen = strlen(numtext);
12313         newblock = FALSE;
12314
12315         /* Print move number */
12316         blank = linelen > 0 && numlen > 0;
12317         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12318             fprintf(f, "\n");
12319             linelen = 0;
12320             blank = 0;
12321         }
12322         if (blank) {
12323             fprintf(f, " ");
12324             linelen++;
12325         }
12326         fprintf(f, "%s", numtext);
12327         linelen += numlen;
12328
12329         /* Get move */
12330         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12331         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12332
12333         /* Print move */
12334         blank = linelen > 0 && movelen > 0;
12335         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12336             fprintf(f, "\n");
12337             linelen = 0;
12338             blank = 0;
12339         }
12340         if (blank) {
12341             fprintf(f, " ");
12342             linelen++;
12343         }
12344         fprintf(f, "%s", move_buffer);
12345         linelen += movelen;
12346
12347         /* [AS] Add PV info if present */
12348         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12349             /* [HGM] add time */
12350             char buf[MSG_SIZ]; int seconds;
12351
12352             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12353
12354             if( seconds <= 0)
12355               buf[0] = 0;
12356             else
12357               if( seconds < 30 )
12358                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12359               else
12360                 {
12361                   seconds = (seconds + 4)/10; // round to full seconds
12362                   if( seconds < 60 )
12363                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12364                   else
12365                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12366                 }
12367
12368             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12369                       pvInfoList[i].score >= 0 ? "+" : "",
12370                       pvInfoList[i].score / 100.0,
12371                       pvInfoList[i].depth,
12372                       buf );
12373
12374             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12375
12376             /* Print score/depth */
12377             blank = linelen > 0 && movelen > 0;
12378             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12379                 fprintf(f, "\n");
12380                 linelen = 0;
12381                 blank = 0;
12382             }
12383             if (blank) {
12384                 fprintf(f, " ");
12385                 linelen++;
12386             }
12387             fprintf(f, "%s", move_buffer);
12388             linelen += movelen;
12389         }
12390
12391         i++;
12392     }
12393
12394     /* Start a new line */
12395     if (linelen > 0) fprintf(f, "\n");
12396
12397     /* Print comments after last move */
12398     if (commentList[i] != NULL) {
12399         fprintf(f, "%s\n", commentList[i]);
12400     }
12401
12402     /* Print result */
12403     if (gameInfo.resultDetails != NULL &&
12404         gameInfo.resultDetails[0] != NULLCHAR) {
12405         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12406                 PGNResult(gameInfo.result));
12407     } else {
12408         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12409     }
12410
12411     fclose(f);
12412     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12413     return TRUE;
12414 }
12415
12416 /* Save game in old style and close the file */
12417 int
12418 SaveGameOldStyle (FILE *f)
12419 {
12420     int i, offset;
12421     time_t tm;
12422
12423     tm = time((time_t *) NULL);
12424
12425     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12426     PrintOpponents(f);
12427
12428     if (backwardMostMove > 0 || startedFromSetupPosition) {
12429         fprintf(f, "\n[--------------\n");
12430         PrintPosition(f, backwardMostMove);
12431         fprintf(f, "--------------]\n");
12432     } else {
12433         fprintf(f, "\n");
12434     }
12435
12436     i = backwardMostMove;
12437     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12438
12439     while (i < forwardMostMove) {
12440         if (commentList[i] != NULL) {
12441             fprintf(f, "[%s]\n", commentList[i]);
12442         }
12443
12444         if ((i % 2) == 1) {
12445             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12446             i++;
12447         } else {
12448             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12449             i++;
12450             if (commentList[i] != NULL) {
12451                 fprintf(f, "\n");
12452                 continue;
12453             }
12454             if (i >= forwardMostMove) {
12455                 fprintf(f, "\n");
12456                 break;
12457             }
12458             fprintf(f, "%s\n", parseList[i]);
12459             i++;
12460         }
12461     }
12462
12463     if (commentList[i] != NULL) {
12464         fprintf(f, "[%s]\n", commentList[i]);
12465     }
12466
12467     /* This isn't really the old style, but it's close enough */
12468     if (gameInfo.resultDetails != NULL &&
12469         gameInfo.resultDetails[0] != NULLCHAR) {
12470         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12471                 gameInfo.resultDetails);
12472     } else {
12473         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12474     }
12475
12476     fclose(f);
12477     return TRUE;
12478 }
12479
12480 /* Save the current game to open file f and close the file */
12481 int
12482 SaveGame (FILE *f, int dummy, char *dummy2)
12483 {
12484     if (gameMode == EditPosition) EditPositionDone(TRUE);
12485     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12486     if (appData.oldSaveStyle)
12487       return SaveGameOldStyle(f);
12488     else
12489       return SaveGamePGN(f);
12490 }
12491
12492 /* Save the current position to the given file */
12493 int
12494 SavePositionToFile (char *filename)
12495 {
12496     FILE *f;
12497     char buf[MSG_SIZ];
12498
12499     if (strcmp(filename, "-") == 0) {
12500         return SavePosition(stdout, 0, NULL);
12501     } else {
12502         f = fopen(filename, "a");
12503         if (f == NULL) {
12504             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12505             DisplayError(buf, errno);
12506             return FALSE;
12507         } else {
12508             safeStrCpy(buf, lastMsg, MSG_SIZ);
12509             DisplayMessage(_("Waiting for access to save file"), "");
12510             flock(fileno(f), LOCK_EX); // [HGM] lock
12511             DisplayMessage(_("Saving position"), "");
12512             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12513             SavePosition(f, 0, NULL);
12514             DisplayMessage(buf, "");
12515             return TRUE;
12516         }
12517     }
12518 }
12519
12520 /* Save the current position to the given open file and close the file */
12521 int
12522 SavePosition (FILE *f, int dummy, char *dummy2)
12523 {
12524     time_t tm;
12525     char *fen;
12526
12527     if (gameMode == EditPosition) EditPositionDone(TRUE);
12528     if (appData.oldSaveStyle) {
12529         tm = time((time_t *) NULL);
12530
12531         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12532         PrintOpponents(f);
12533         fprintf(f, "[--------------\n");
12534         PrintPosition(f, currentMove);
12535         fprintf(f, "--------------]\n");
12536     } else {
12537         fen = PositionToFEN(currentMove, NULL);
12538         fprintf(f, "%s\n", fen);
12539         free(fen);
12540     }
12541     fclose(f);
12542     return TRUE;
12543 }
12544
12545 void
12546 ReloadCmailMsgEvent (int unregister)
12547 {
12548 #if !WIN32
12549     static char *inFilename = NULL;
12550     static char *outFilename;
12551     int i;
12552     struct stat inbuf, outbuf;
12553     int status;
12554
12555     /* Any registered moves are unregistered if unregister is set, */
12556     /* i.e. invoked by the signal handler */
12557     if (unregister) {
12558         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12559             cmailMoveRegistered[i] = FALSE;
12560             if (cmailCommentList[i] != NULL) {
12561                 free(cmailCommentList[i]);
12562                 cmailCommentList[i] = NULL;
12563             }
12564         }
12565         nCmailMovesRegistered = 0;
12566     }
12567
12568     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12569         cmailResult[i] = CMAIL_NOT_RESULT;
12570     }
12571     nCmailResults = 0;
12572
12573     if (inFilename == NULL) {
12574         /* Because the filenames are static they only get malloced once  */
12575         /* and they never get freed                                      */
12576         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12577         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12578
12579         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12580         sprintf(outFilename, "%s.out", appData.cmailGameName);
12581     }
12582
12583     status = stat(outFilename, &outbuf);
12584     if (status < 0) {
12585         cmailMailedMove = FALSE;
12586     } else {
12587         status = stat(inFilename, &inbuf);
12588         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12589     }
12590
12591     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12592        counts the games, notes how each one terminated, etc.
12593
12594        It would be nice to remove this kludge and instead gather all
12595        the information while building the game list.  (And to keep it
12596        in the game list nodes instead of having a bunch of fixed-size
12597        parallel arrays.)  Note this will require getting each game's
12598        termination from the PGN tags, as the game list builder does
12599        not process the game moves.  --mann
12600        */
12601     cmailMsgLoaded = TRUE;
12602     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12603
12604     /* Load first game in the file or popup game menu */
12605     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12606
12607 #endif /* !WIN32 */
12608     return;
12609 }
12610
12611 int
12612 RegisterMove ()
12613 {
12614     FILE *f;
12615     char string[MSG_SIZ];
12616
12617     if (   cmailMailedMove
12618         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12619         return TRUE;            /* Allow free viewing  */
12620     }
12621
12622     /* Unregister move to ensure that we don't leave RegisterMove        */
12623     /* with the move registered when the conditions for registering no   */
12624     /* longer hold                                                       */
12625     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12626         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12627         nCmailMovesRegistered --;
12628
12629         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12630           {
12631               free(cmailCommentList[lastLoadGameNumber - 1]);
12632               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12633           }
12634     }
12635
12636     if (cmailOldMove == -1) {
12637         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12638         return FALSE;
12639     }
12640
12641     if (currentMove > cmailOldMove + 1) {
12642         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12643         return FALSE;
12644     }
12645
12646     if (currentMove < cmailOldMove) {
12647         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12648         return FALSE;
12649     }
12650
12651     if (forwardMostMove > currentMove) {
12652         /* Silently truncate extra moves */
12653         TruncateGame();
12654     }
12655
12656     if (   (currentMove == cmailOldMove + 1)
12657         || (   (currentMove == cmailOldMove)
12658             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12659                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12660         if (gameInfo.result != GameUnfinished) {
12661             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12662         }
12663
12664         if (commentList[currentMove] != NULL) {
12665             cmailCommentList[lastLoadGameNumber - 1]
12666               = StrSave(commentList[currentMove]);
12667         }
12668         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12669
12670         if (appData.debugMode)
12671           fprintf(debugFP, "Saving %s for game %d\n",
12672                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12673
12674         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12675
12676         f = fopen(string, "w");
12677         if (appData.oldSaveStyle) {
12678             SaveGameOldStyle(f); /* also closes the file */
12679
12680             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12681             f = fopen(string, "w");
12682             SavePosition(f, 0, NULL); /* also closes the file */
12683         } else {
12684             fprintf(f, "{--------------\n");
12685             PrintPosition(f, currentMove);
12686             fprintf(f, "--------------}\n\n");
12687
12688             SaveGame(f, 0, NULL); /* also closes the file*/
12689         }
12690
12691         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12692         nCmailMovesRegistered ++;
12693     } else if (nCmailGames == 1) {
12694         DisplayError(_("You have not made a move yet"), 0);
12695         return FALSE;
12696     }
12697
12698     return TRUE;
12699 }
12700
12701 void
12702 MailMoveEvent ()
12703 {
12704 #if !WIN32
12705     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12706     FILE *commandOutput;
12707     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12708     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12709     int nBuffers;
12710     int i;
12711     int archived;
12712     char *arcDir;
12713
12714     if (! cmailMsgLoaded) {
12715         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12716         return;
12717     }
12718
12719     if (nCmailGames == nCmailResults) {
12720         DisplayError(_("No unfinished games"), 0);
12721         return;
12722     }
12723
12724 #if CMAIL_PROHIBIT_REMAIL
12725     if (cmailMailedMove) {
12726       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);
12727         DisplayError(msg, 0);
12728         return;
12729     }
12730 #endif
12731
12732     if (! (cmailMailedMove || RegisterMove())) return;
12733
12734     if (   cmailMailedMove
12735         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12736       snprintf(string, MSG_SIZ, partCommandString,
12737                appData.debugMode ? " -v" : "", appData.cmailGameName);
12738         commandOutput = popen(string, "r");
12739
12740         if (commandOutput == NULL) {
12741             DisplayError(_("Failed to invoke cmail"), 0);
12742         } else {
12743             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12744                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12745             }
12746             if (nBuffers > 1) {
12747                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12748                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12749                 nBytes = MSG_SIZ - 1;
12750             } else {
12751                 (void) memcpy(msg, buffer, nBytes);
12752             }
12753             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12754
12755             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12756                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12757
12758                 archived = TRUE;
12759                 for (i = 0; i < nCmailGames; i ++) {
12760                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12761                         archived = FALSE;
12762                     }
12763                 }
12764                 if (   archived
12765                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12766                         != NULL)) {
12767                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12768                            arcDir,
12769                            appData.cmailGameName,
12770                            gameInfo.date);
12771                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12772                     cmailMsgLoaded = FALSE;
12773                 }
12774             }
12775
12776             DisplayInformation(msg);
12777             pclose(commandOutput);
12778         }
12779     } else {
12780         if ((*cmailMsg) != '\0') {
12781             DisplayInformation(cmailMsg);
12782         }
12783     }
12784
12785     return;
12786 #endif /* !WIN32 */
12787 }
12788
12789 char *
12790 CmailMsg ()
12791 {
12792 #if WIN32
12793     return NULL;
12794 #else
12795     int  prependComma = 0;
12796     char number[5];
12797     char string[MSG_SIZ];       /* Space for game-list */
12798     int  i;
12799
12800     if (!cmailMsgLoaded) return "";
12801
12802     if (cmailMailedMove) {
12803       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12804     } else {
12805         /* Create a list of games left */
12806       snprintf(string, MSG_SIZ, "[");
12807         for (i = 0; i < nCmailGames; i ++) {
12808             if (! (   cmailMoveRegistered[i]
12809                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12810                 if (prependComma) {
12811                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12812                 } else {
12813                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12814                     prependComma = 1;
12815                 }
12816
12817                 strcat(string, number);
12818             }
12819         }
12820         strcat(string, "]");
12821
12822         if (nCmailMovesRegistered + nCmailResults == 0) {
12823             switch (nCmailGames) {
12824               case 1:
12825                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12826                 break;
12827
12828               case 2:
12829                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12830                 break;
12831
12832               default:
12833                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12834                          nCmailGames);
12835                 break;
12836             }
12837         } else {
12838             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12839               case 1:
12840                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12841                          string);
12842                 break;
12843
12844               case 0:
12845                 if (nCmailResults == nCmailGames) {
12846                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12847                 } else {
12848                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12849                 }
12850                 break;
12851
12852               default:
12853                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12854                          string);
12855             }
12856         }
12857     }
12858     return cmailMsg;
12859 #endif /* WIN32 */
12860 }
12861
12862 void
12863 ResetGameEvent ()
12864 {
12865     if (gameMode == Training)
12866       SetTrainingModeOff();
12867
12868     Reset(TRUE, TRUE);
12869     cmailMsgLoaded = FALSE;
12870     if (appData.icsActive) {
12871       SendToICS(ics_prefix);
12872       SendToICS("refresh\n");
12873     }
12874 }
12875
12876 void
12877 ExitEvent (int status)
12878 {
12879     exiting++;
12880     if (exiting > 2) {
12881       /* Give up on clean exit */
12882       exit(status);
12883     }
12884     if (exiting > 1) {
12885       /* Keep trying for clean exit */
12886       return;
12887     }
12888
12889     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12890
12891     if (telnetISR != NULL) {
12892       RemoveInputSource(telnetISR);
12893     }
12894     if (icsPR != NoProc) {
12895       DestroyChildProcess(icsPR, TRUE);
12896     }
12897
12898     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12899     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12900
12901     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12902     /* make sure this other one finishes before killing it!                  */
12903     if(endingGame) { int count = 0;
12904         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12905         while(endingGame && count++ < 10) DoSleep(1);
12906         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12907     }
12908
12909     /* Kill off chess programs */
12910     if (first.pr != NoProc) {
12911         ExitAnalyzeMode();
12912
12913         DoSleep( appData.delayBeforeQuit );
12914         SendToProgram("quit\n", &first);
12915         DoSleep( appData.delayAfterQuit );
12916         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12917     }
12918     if (second.pr != NoProc) {
12919         DoSleep( appData.delayBeforeQuit );
12920         SendToProgram("quit\n", &second);
12921         DoSleep( appData.delayAfterQuit );
12922         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12923     }
12924     if (first.isr != NULL) {
12925         RemoveInputSource(first.isr);
12926     }
12927     if (second.isr != NULL) {
12928         RemoveInputSource(second.isr);
12929     }
12930
12931     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12932     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12933
12934     ShutDownFrontEnd();
12935     exit(status);
12936 }
12937
12938 void
12939 PauseEvent ()
12940 {
12941     if (appData.debugMode)
12942         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12943     if (pausing) {
12944         pausing = FALSE;
12945         ModeHighlight();
12946         if (gameMode == MachinePlaysWhite ||
12947             gameMode == MachinePlaysBlack) {
12948             StartClocks();
12949         } else {
12950             DisplayBothClocks();
12951         }
12952         if (gameMode == PlayFromGameFile) {
12953             if (appData.timeDelay >= 0)
12954                 AutoPlayGameLoop();
12955         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12956             Reset(FALSE, TRUE);
12957             SendToICS(ics_prefix);
12958             SendToICS("refresh\n");
12959         } else if (currentMove < forwardMostMove) {
12960             ForwardInner(forwardMostMove);
12961         }
12962         pauseExamInvalid = FALSE;
12963     } else {
12964         switch (gameMode) {
12965           default:
12966             return;
12967           case IcsExamining:
12968             pauseExamForwardMostMove = forwardMostMove;
12969             pauseExamInvalid = FALSE;
12970             /* fall through */
12971           case IcsObserving:
12972           case IcsPlayingWhite:
12973           case IcsPlayingBlack:
12974             pausing = TRUE;
12975             ModeHighlight();
12976             return;
12977           case PlayFromGameFile:
12978             (void) StopLoadGameTimer();
12979             pausing = TRUE;
12980             ModeHighlight();
12981             break;
12982           case BeginningOfGame:
12983             if (appData.icsActive) return;
12984             /* else fall through */
12985           case MachinePlaysWhite:
12986           case MachinePlaysBlack:
12987           case TwoMachinesPlay:
12988             if (forwardMostMove == 0)
12989               return;           /* don't pause if no one has moved */
12990             if ((gameMode == MachinePlaysWhite &&
12991                  !WhiteOnMove(forwardMostMove)) ||
12992                 (gameMode == MachinePlaysBlack &&
12993                  WhiteOnMove(forwardMostMove))) {
12994                 StopClocks();
12995             }
12996             pausing = TRUE;
12997             ModeHighlight();
12998             break;
12999         }
13000     }
13001 }
13002
13003 void
13004 EditCommentEvent ()
13005 {
13006     char title[MSG_SIZ];
13007
13008     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13009       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13010     } else {
13011       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13012                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13013                parseList[currentMove - 1]);
13014     }
13015
13016     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13017 }
13018
13019
13020 void
13021 EditTagsEvent ()
13022 {
13023     char *tags = PGNTags(&gameInfo);
13024     bookUp = FALSE;
13025     EditTagsPopUp(tags, NULL);
13026     free(tags);
13027 }
13028
13029 void
13030 AnalyzeModeEvent ()
13031 {
13032     if (appData.noChessProgram || gameMode == AnalyzeMode)
13033       return;
13034
13035     if (gameMode != AnalyzeFile) {
13036         if (!appData.icsEngineAnalyze) {
13037                EditGameEvent();
13038                if (gameMode != EditGame) return;
13039         }
13040         ResurrectChessProgram();
13041         SendToProgram("analyze\n", &first);
13042         first.analyzing = TRUE;
13043         /*first.maybeThinking = TRUE;*/
13044         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13045         EngineOutputPopUp();
13046     }
13047     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13048     pausing = FALSE;
13049     ModeHighlight();
13050     SetGameInfo();
13051
13052     StartAnalysisClock();
13053     GetTimeMark(&lastNodeCountTime);
13054     lastNodeCount = 0;
13055 }
13056
13057 void
13058 AnalyzeFileEvent ()
13059 {
13060     if (appData.noChessProgram || gameMode == AnalyzeFile)
13061       return;
13062
13063     if (gameMode != AnalyzeMode) {
13064         EditGameEvent();
13065         if (gameMode != EditGame) return;
13066         ResurrectChessProgram();
13067         SendToProgram("analyze\n", &first);
13068         first.analyzing = TRUE;
13069         /*first.maybeThinking = TRUE;*/
13070         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13071         EngineOutputPopUp();
13072     }
13073     gameMode = AnalyzeFile;
13074     pausing = FALSE;
13075     ModeHighlight();
13076     SetGameInfo();
13077
13078     StartAnalysisClock();
13079     GetTimeMark(&lastNodeCountTime);
13080     lastNodeCount = 0;
13081     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13082 }
13083
13084 void
13085 MachineWhiteEvent ()
13086 {
13087     char buf[MSG_SIZ];
13088     char *bookHit = NULL;
13089
13090     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13091       return;
13092
13093
13094     if (gameMode == PlayFromGameFile ||
13095         gameMode == TwoMachinesPlay  ||
13096         gameMode == Training         ||
13097         gameMode == AnalyzeMode      ||
13098         gameMode == EndOfGame)
13099         EditGameEvent();
13100
13101     if (gameMode == EditPosition)
13102         EditPositionDone(TRUE);
13103
13104     if (!WhiteOnMove(currentMove)) {
13105         DisplayError(_("It is not White's turn"), 0);
13106         return;
13107     }
13108
13109     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13110       ExitAnalyzeMode();
13111
13112     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13113         gameMode == AnalyzeFile)
13114         TruncateGame();
13115
13116     ResurrectChessProgram();    /* in case it isn't running */
13117     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13118         gameMode = MachinePlaysWhite;
13119         ResetClocks();
13120     } else
13121     gameMode = MachinePlaysWhite;
13122     pausing = FALSE;
13123     ModeHighlight();
13124     SetGameInfo();
13125     snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
13126     DisplayTitle(buf);
13127     if (first.sendName) {
13128       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13129       SendToProgram(buf, &first);
13130     }
13131     if (first.sendTime) {
13132       if (first.useColors) {
13133         SendToProgram("black\n", &first); /*gnu kludge*/
13134       }
13135       SendTimeRemaining(&first, TRUE);
13136     }
13137     if (first.useColors) {
13138       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13139     }
13140     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13141     SetMachineThinkingEnables();
13142     first.maybeThinking = TRUE;
13143     StartClocks();
13144     firstMove = FALSE;
13145
13146     if (appData.autoFlipView && !flipView) {
13147       flipView = !flipView;
13148       DrawPosition(FALSE, NULL);
13149       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13150     }
13151
13152     if(bookHit) { // [HGM] book: simulate book reply
13153         static char bookMove[MSG_SIZ]; // a bit generous?
13154
13155         programStats.nodes = programStats.depth = programStats.time =
13156         programStats.score = programStats.got_only_move = 0;
13157         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13158
13159         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13160         strcat(bookMove, bookHit);
13161         HandleMachineMove(bookMove, &first);
13162     }
13163 }
13164
13165 void
13166 MachineBlackEvent ()
13167 {
13168   char buf[MSG_SIZ];
13169   char *bookHit = NULL;
13170
13171     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13172         return;
13173
13174
13175     if (gameMode == PlayFromGameFile ||
13176         gameMode == TwoMachinesPlay  ||
13177         gameMode == Training         ||
13178         gameMode == AnalyzeMode      ||
13179         gameMode == EndOfGame)
13180         EditGameEvent();
13181
13182     if (gameMode == EditPosition)
13183         EditPositionDone(TRUE);
13184
13185     if (WhiteOnMove(currentMove)) {
13186         DisplayError(_("It is not Black's turn"), 0);
13187         return;
13188     }
13189
13190     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13191       ExitAnalyzeMode();
13192
13193     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13194         gameMode == AnalyzeFile)
13195         TruncateGame();
13196
13197     ResurrectChessProgram();    /* in case it isn't running */
13198     gameMode = MachinePlaysBlack;
13199     pausing = FALSE;
13200     ModeHighlight();
13201     SetGameInfo();
13202     snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
13203     DisplayTitle(buf);
13204     if (first.sendName) {
13205       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13206       SendToProgram(buf, &first);
13207     }
13208     if (first.sendTime) {
13209       if (first.useColors) {
13210         SendToProgram("white\n", &first); /*gnu kludge*/
13211       }
13212       SendTimeRemaining(&first, FALSE);
13213     }
13214     if (first.useColors) {
13215       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13216     }
13217     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13218     SetMachineThinkingEnables();
13219     first.maybeThinking = TRUE;
13220     StartClocks();
13221
13222     if (appData.autoFlipView && flipView) {
13223       flipView = !flipView;
13224       DrawPosition(FALSE, NULL);
13225       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13226     }
13227     if(bookHit) { // [HGM] book: simulate book reply
13228         static char bookMove[MSG_SIZ]; // a bit generous?
13229
13230         programStats.nodes = programStats.depth = programStats.time =
13231         programStats.score = programStats.got_only_move = 0;
13232         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13233
13234         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13235         strcat(bookMove, bookHit);
13236         HandleMachineMove(bookMove, &first);
13237     }
13238 }
13239
13240
13241 void
13242 DisplayTwoMachinesTitle ()
13243 {
13244     char buf[MSG_SIZ];
13245     if (appData.matchGames > 0) {
13246         if(appData.tourneyFile[0]) {
13247           snprintf(buf, MSG_SIZ, _("%s vs. %s (%d/%d%s)"),
13248                    gameInfo.white, gameInfo.black,
13249                    nextGame+1, appData.matchGames+1,
13250                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13251         } else 
13252         if (first.twoMachinesColor[0] == 'w') {
13253           snprintf(buf, MSG_SIZ, _("%s vs. %s (%d-%d-%d)"),
13254                    gameInfo.white, gameInfo.black,
13255                    first.matchWins, second.matchWins,
13256                    matchGame - 1 - (first.matchWins + second.matchWins));
13257         } else {
13258           snprintf(buf, MSG_SIZ, _("%s vs. %s (%d-%d-%d)"),
13259                    gameInfo.white, gameInfo.black,
13260                    second.matchWins, first.matchWins,
13261                    matchGame - 1 - (first.matchWins + second.matchWins));
13262         }
13263     } else {
13264       snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
13265     }
13266     DisplayTitle(buf);
13267 }
13268
13269 void
13270 SettingsMenuIfReady ()
13271 {
13272   if (second.lastPing != second.lastPong) {
13273     DisplayMessage("", _("Waiting for second chess program"));
13274     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13275     return;
13276   }
13277   ThawUI();
13278   DisplayMessage("", "");
13279   SettingsPopUp(&second);
13280 }
13281
13282 int
13283 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13284 {
13285     char buf[MSG_SIZ];
13286     if (cps->pr == NoProc) {
13287         StartChessProgram(cps);
13288         if (cps->protocolVersion == 1) {
13289           retry();
13290         } else {
13291           /* kludge: allow timeout for initial "feature" command */
13292           FreezeUI();
13293           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13294           DisplayMessage("", buf);
13295           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13296         }
13297         return 1;
13298     }
13299     return 0;
13300 }
13301
13302 void
13303 TwoMachinesEvent P((void))
13304 {
13305     int i;
13306     char buf[MSG_SIZ];
13307     ChessProgramState *onmove;
13308     char *bookHit = NULL;
13309     static int stalling = 0;
13310     TimeMark now;
13311     long wait;
13312
13313     if (appData.noChessProgram) return;
13314
13315     switch (gameMode) {
13316       case TwoMachinesPlay:
13317         return;
13318       case MachinePlaysWhite:
13319       case MachinePlaysBlack:
13320         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13321             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13322             return;
13323         }
13324         /* fall through */
13325       case BeginningOfGame:
13326       case PlayFromGameFile:
13327       case EndOfGame:
13328         EditGameEvent();
13329         if (gameMode != EditGame) return;
13330         break;
13331       case EditPosition:
13332         EditPositionDone(TRUE);
13333         break;
13334       case AnalyzeMode:
13335       case AnalyzeFile:
13336         ExitAnalyzeMode();
13337         break;
13338       case EditGame:
13339       default:
13340         break;
13341     }
13342
13343 //    forwardMostMove = currentMove;
13344     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13345
13346     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13347
13348     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13349     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13350       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13351       return;
13352     }
13353     if(!stalling) {
13354       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13355       SendToProgram("force\n", &second);
13356       stalling = 1;
13357       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13358       return;
13359     }
13360     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13361     if(appData.matchPause>10000 || appData.matchPause<10)
13362                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13363     wait = SubtractTimeMarks(&now, &pauseStart);
13364     if(wait < appData.matchPause) {
13365         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13366         return;
13367     }
13368     stalling = 0;
13369     DisplayMessage("", "");
13370     if (startedFromSetupPosition) {
13371         SendBoard(&second, backwardMostMove);
13372     if (appData.debugMode) {
13373         fprintf(debugFP, "Two Machines\n");
13374     }
13375     }
13376     for (i = backwardMostMove; i < forwardMostMove; i++) {
13377         SendMoveToProgram(i, &second);
13378     }
13379
13380     gameMode = TwoMachinesPlay;
13381     pausing = FALSE;
13382     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13383     SetGameInfo();
13384     DisplayTwoMachinesTitle();
13385     firstMove = TRUE;
13386     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13387         onmove = &first;
13388     } else {
13389         onmove = &second;
13390     }
13391     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13392     SendToProgram(first.computerString, &first);
13393     if (first.sendName) {
13394       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13395       SendToProgram(buf, &first);
13396     }
13397     SendToProgram(second.computerString, &second);
13398     if (second.sendName) {
13399       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13400       SendToProgram(buf, &second);
13401     }
13402
13403     ResetClocks();
13404     if (!first.sendTime || !second.sendTime) {
13405         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13406         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13407     }
13408     if (onmove->sendTime) {
13409       if (onmove->useColors) {
13410         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13411       }
13412       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13413     }
13414     if (onmove->useColors) {
13415       SendToProgram(onmove->twoMachinesColor, onmove);
13416     }
13417     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13418 //    SendToProgram("go\n", onmove);
13419     onmove->maybeThinking = TRUE;
13420     SetMachineThinkingEnables();
13421
13422     StartClocks();
13423
13424     if(bookHit) { // [HGM] book: simulate book reply
13425         static char bookMove[MSG_SIZ]; // a bit generous?
13426
13427         programStats.nodes = programStats.depth = programStats.time =
13428         programStats.score = programStats.got_only_move = 0;
13429         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13430
13431         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13432         strcat(bookMove, bookHit);
13433         savedMessage = bookMove; // args for deferred call
13434         savedState = onmove;
13435         ScheduleDelayedEvent(DeferredBookMove, 1);
13436     }
13437 }
13438
13439 void
13440 TrainingEvent ()
13441 {
13442     if (gameMode == Training) {
13443       SetTrainingModeOff();
13444       gameMode = PlayFromGameFile;
13445       DisplayMessage("", _("Training mode off"));
13446     } else {
13447       gameMode = Training;
13448       animateTraining = appData.animate;
13449
13450       /* make sure we are not already at the end of the game */
13451       if (currentMove < forwardMostMove) {
13452         SetTrainingModeOn();
13453         DisplayMessage("", _("Training mode on"));
13454       } else {
13455         gameMode = PlayFromGameFile;
13456         DisplayError(_("Already at end of game"), 0);
13457       }
13458     }
13459     ModeHighlight();
13460 }
13461
13462 void
13463 IcsClientEvent ()
13464 {
13465     if (!appData.icsActive) return;
13466     switch (gameMode) {
13467       case IcsPlayingWhite:
13468       case IcsPlayingBlack:
13469       case IcsObserving:
13470       case IcsIdle:
13471       case BeginningOfGame:
13472       case IcsExamining:
13473         return;
13474
13475       case EditGame:
13476         break;
13477
13478       case EditPosition:
13479         EditPositionDone(TRUE);
13480         break;
13481
13482       case AnalyzeMode:
13483       case AnalyzeFile:
13484         ExitAnalyzeMode();
13485         break;
13486
13487       default:
13488         EditGameEvent();
13489         break;
13490     }
13491
13492     gameMode = IcsIdle;
13493     ModeHighlight();
13494     return;
13495 }
13496
13497 void
13498 EditGameEvent ()
13499 {
13500     int i;
13501
13502     switch (gameMode) {
13503       case Training:
13504         SetTrainingModeOff();
13505         break;
13506       case MachinePlaysWhite:
13507       case MachinePlaysBlack:
13508       case BeginningOfGame:
13509         SendToProgram("force\n", &first);
13510         SetUserThinkingEnables();
13511         break;
13512       case PlayFromGameFile:
13513         (void) StopLoadGameTimer();
13514         if (gameFileFP != NULL) {
13515             gameFileFP = NULL;
13516         }
13517         break;
13518       case EditPosition:
13519         EditPositionDone(TRUE);
13520         break;
13521       case AnalyzeMode:
13522       case AnalyzeFile:
13523         ExitAnalyzeMode();
13524         SendToProgram("force\n", &first);
13525         break;
13526       case TwoMachinesPlay:
13527         GameEnds(EndOfFile, NULL, GE_PLAYER);
13528         ResurrectChessProgram();
13529         SetUserThinkingEnables();
13530         break;
13531       case EndOfGame:
13532         ResurrectChessProgram();
13533         break;
13534       case IcsPlayingBlack:
13535       case IcsPlayingWhite:
13536         DisplayError(_("Warning: You are still playing a game"), 0);
13537         break;
13538       case IcsObserving:
13539         DisplayError(_("Warning: You are still observing a game"), 0);
13540         break;
13541       case IcsExamining:
13542         DisplayError(_("Warning: You are still examining a game"), 0);
13543         break;
13544       case IcsIdle:
13545         break;
13546       case EditGame:
13547       default:
13548         return;
13549     }
13550
13551     pausing = FALSE;
13552     StopClocks();
13553     first.offeredDraw = second.offeredDraw = 0;
13554
13555     if (gameMode == PlayFromGameFile) {
13556         whiteTimeRemaining = timeRemaining[0][currentMove];
13557         blackTimeRemaining = timeRemaining[1][currentMove];
13558         DisplayTitle("");
13559     }
13560
13561     if (gameMode == MachinePlaysWhite ||
13562         gameMode == MachinePlaysBlack ||
13563         gameMode == TwoMachinesPlay ||
13564         gameMode == EndOfGame) {
13565         i = forwardMostMove;
13566         while (i > currentMove) {
13567             SendToProgram("undo\n", &first);
13568             i--;
13569         }
13570         if(!adjustedClock) {
13571         whiteTimeRemaining = timeRemaining[0][currentMove];
13572         blackTimeRemaining = timeRemaining[1][currentMove];
13573         DisplayBothClocks();
13574         }
13575         if (whiteFlag || blackFlag) {
13576             whiteFlag = blackFlag = 0;
13577         }
13578         DisplayTitle("");
13579     }
13580
13581     gameMode = EditGame;
13582     ModeHighlight();
13583     SetGameInfo();
13584 }
13585
13586
13587 void
13588 EditPositionEvent ()
13589 {
13590     if (gameMode == EditPosition) {
13591         EditGameEvent();
13592         return;
13593     }
13594
13595     EditGameEvent();
13596     if (gameMode != EditGame) return;
13597
13598     gameMode = EditPosition;
13599     ModeHighlight();
13600     SetGameInfo();
13601     if (currentMove > 0)
13602       CopyBoard(boards[0], boards[currentMove]);
13603
13604     blackPlaysFirst = !WhiteOnMove(currentMove);
13605     ResetClocks();
13606     currentMove = forwardMostMove = backwardMostMove = 0;
13607     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13608     DisplayMove(-1);
13609 }
13610
13611 void
13612 ExitAnalyzeMode ()
13613 {
13614     /* [DM] icsEngineAnalyze - possible call from other functions */
13615     if (appData.icsEngineAnalyze) {
13616         appData.icsEngineAnalyze = FALSE;
13617
13618         DisplayMessage("",_("Close ICS engine analyze..."));
13619     }
13620     if (first.analysisSupport && first.analyzing) {
13621       SendToProgram("exit\n", &first);
13622       first.analyzing = FALSE;
13623     }
13624     thinkOutput[0] = NULLCHAR;
13625 }
13626
13627 void
13628 EditPositionDone (Boolean fakeRights)
13629 {
13630     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13631
13632     startedFromSetupPosition = TRUE;
13633     InitChessProgram(&first, FALSE);
13634     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13635       boards[0][EP_STATUS] = EP_NONE;
13636       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13637     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13638         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13639         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13640       } else boards[0][CASTLING][2] = NoRights;
13641     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13642         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13643         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13644       } else boards[0][CASTLING][5] = NoRights;
13645     }
13646     SendToProgram("force\n", &first);
13647     if (blackPlaysFirst) {
13648         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13649         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13650         currentMove = forwardMostMove = backwardMostMove = 1;
13651         CopyBoard(boards[1], boards[0]);
13652     } else {
13653         currentMove = forwardMostMove = backwardMostMove = 0;
13654     }
13655     SendBoard(&first, forwardMostMove);
13656     if (appData.debugMode) {
13657         fprintf(debugFP, "EditPosDone\n");
13658     }
13659     DisplayTitle("");
13660     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13661     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13662     gameMode = EditGame;
13663     ModeHighlight();
13664     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13665     ClearHighlights(); /* [AS] */
13666 }
13667
13668 /* Pause for `ms' milliseconds */
13669 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13670 void
13671 TimeDelay (long ms)
13672 {
13673     TimeMark m1, m2;
13674
13675     GetTimeMark(&m1);
13676     do {
13677         GetTimeMark(&m2);
13678     } while (SubtractTimeMarks(&m2, &m1) < ms);
13679 }
13680
13681 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13682 void
13683 SendMultiLineToICS (char *buf)
13684 {
13685     char temp[MSG_SIZ+1], *p;
13686     int len;
13687
13688     len = strlen(buf);
13689     if (len > MSG_SIZ)
13690       len = MSG_SIZ;
13691
13692     strncpy(temp, buf, len);
13693     temp[len] = 0;
13694
13695     p = temp;
13696     while (*p) {
13697         if (*p == '\n' || *p == '\r')
13698           *p = ' ';
13699         ++p;
13700     }
13701
13702     strcat(temp, "\n");
13703     SendToICS(temp);
13704     SendToPlayer(temp, strlen(temp));
13705 }
13706
13707 void
13708 SetWhiteToPlayEvent ()
13709 {
13710     if (gameMode == EditPosition) {
13711         blackPlaysFirst = FALSE;
13712         DisplayBothClocks();    /* works because currentMove is 0 */
13713     } else if (gameMode == IcsExamining) {
13714         SendToICS(ics_prefix);
13715         SendToICS("tomove white\n");
13716     }
13717 }
13718
13719 void
13720 SetBlackToPlayEvent ()
13721 {
13722     if (gameMode == EditPosition) {
13723         blackPlaysFirst = TRUE;
13724         currentMove = 1;        /* kludge */
13725         DisplayBothClocks();
13726         currentMove = 0;
13727     } else if (gameMode == IcsExamining) {
13728         SendToICS(ics_prefix);
13729         SendToICS("tomove black\n");
13730     }
13731 }
13732
13733 void
13734 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13735 {
13736     char buf[MSG_SIZ];
13737     ChessSquare piece = boards[0][y][x];
13738
13739     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13740
13741     switch (selection) {
13742       case ClearBoard:
13743         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13744             SendToICS(ics_prefix);
13745             SendToICS("bsetup clear\n");
13746         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13747             SendToICS(ics_prefix);
13748             SendToICS("clearboard\n");
13749         } else {
13750             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13751                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13752                 for (y = 0; y < BOARD_HEIGHT; y++) {
13753                     if (gameMode == IcsExamining) {
13754                         if (boards[currentMove][y][x] != EmptySquare) {
13755                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13756                                     AAA + x, ONE + y);
13757                             SendToICS(buf);
13758                         }
13759                     } else {
13760                         boards[0][y][x] = p;
13761                     }
13762                 }
13763             }
13764         }
13765         if (gameMode == EditPosition) {
13766             DrawPosition(FALSE, boards[0]);
13767         }
13768         break;
13769
13770       case WhitePlay:
13771         SetWhiteToPlayEvent();
13772         break;
13773
13774       case BlackPlay:
13775         SetBlackToPlayEvent();
13776         break;
13777
13778       case EmptySquare:
13779         if (gameMode == IcsExamining) {
13780             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13781             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13782             SendToICS(buf);
13783         } else {
13784             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13785                 if(x == BOARD_LEFT-2) {
13786                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13787                     boards[0][y][1] = 0;
13788                 } else
13789                 if(x == BOARD_RGHT+1) {
13790                     if(y >= gameInfo.holdingsSize) break;
13791                     boards[0][y][BOARD_WIDTH-2] = 0;
13792                 } else break;
13793             }
13794             boards[0][y][x] = EmptySquare;
13795             DrawPosition(FALSE, boards[0]);
13796         }
13797         break;
13798
13799       case PromotePiece:
13800         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13801            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13802             selection = (ChessSquare) (PROMOTED piece);
13803         } else if(piece == EmptySquare) selection = WhiteSilver;
13804         else selection = (ChessSquare)((int)piece - 1);
13805         goto defaultlabel;
13806
13807       case DemotePiece:
13808         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13809            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13810             selection = (ChessSquare) (DEMOTED piece);
13811         } else if(piece == EmptySquare) selection = BlackSilver;
13812         else selection = (ChessSquare)((int)piece + 1);
13813         goto defaultlabel;
13814
13815       case WhiteQueen:
13816       case BlackQueen:
13817         if(gameInfo.variant == VariantShatranj ||
13818            gameInfo.variant == VariantXiangqi  ||
13819            gameInfo.variant == VariantCourier  ||
13820            gameInfo.variant == VariantMakruk     )
13821             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13822         goto defaultlabel;
13823
13824       case WhiteKing:
13825       case BlackKing:
13826         if(gameInfo.variant == VariantXiangqi)
13827             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13828         if(gameInfo.variant == VariantKnightmate)
13829             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13830       default:
13831         defaultlabel:
13832         if (gameMode == IcsExamining) {
13833             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13834             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13835                      PieceToChar(selection), AAA + x, ONE + y);
13836             SendToICS(buf);
13837         } else {
13838             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13839                 int n;
13840                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13841                     n = PieceToNumber(selection - BlackPawn);
13842                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13843                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13844                     boards[0][BOARD_HEIGHT-1-n][1]++;
13845                 } else
13846                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13847                     n = PieceToNumber(selection);
13848                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13849                     boards[0][n][BOARD_WIDTH-1] = selection;
13850                     boards[0][n][BOARD_WIDTH-2]++;
13851                 }
13852             } else
13853             boards[0][y][x] = selection;
13854             DrawPosition(TRUE, boards[0]);
13855         }
13856         break;
13857     }
13858 }
13859
13860
13861 void
13862 DropMenuEvent (ChessSquare selection, int x, int y)
13863 {
13864     ChessMove moveType;
13865
13866     switch (gameMode) {
13867       case IcsPlayingWhite:
13868       case MachinePlaysBlack:
13869         if (!WhiteOnMove(currentMove)) {
13870             DisplayMoveError(_("It is Black's turn"));
13871             return;
13872         }
13873         moveType = WhiteDrop;
13874         break;
13875       case IcsPlayingBlack:
13876       case MachinePlaysWhite:
13877         if (WhiteOnMove(currentMove)) {
13878             DisplayMoveError(_("It is White's turn"));
13879             return;
13880         }
13881         moveType = BlackDrop;
13882         break;
13883       case EditGame:
13884         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13885         break;
13886       default:
13887         return;
13888     }
13889
13890     if (moveType == BlackDrop && selection < BlackPawn) {
13891       selection = (ChessSquare) ((int) selection
13892                                  + (int) BlackPawn - (int) WhitePawn);
13893     }
13894     if (boards[currentMove][y][x] != EmptySquare) {
13895         DisplayMoveError(_("That square is occupied"));
13896         return;
13897     }
13898
13899     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13900 }
13901
13902 void
13903 AcceptEvent ()
13904 {
13905     /* Accept a pending offer of any kind from opponent */
13906
13907     if (appData.icsActive) {
13908         SendToICS(ics_prefix);
13909         SendToICS("accept\n");
13910     } else if (cmailMsgLoaded) {
13911         if (currentMove == cmailOldMove &&
13912             commentList[cmailOldMove] != NULL &&
13913             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13914                    "Black offers a draw" : "White offers a draw")) {
13915             TruncateGame();
13916             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13917             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13918         } else {
13919             DisplayError(_("There is no pending offer on this move"), 0);
13920             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13921         }
13922     } else {
13923         /* Not used for offers from chess program */
13924     }
13925 }
13926
13927 void
13928 DeclineEvent ()
13929 {
13930     /* Decline a pending offer of any kind from opponent */
13931
13932     if (appData.icsActive) {
13933         SendToICS(ics_prefix);
13934         SendToICS("decline\n");
13935     } else if (cmailMsgLoaded) {
13936         if (currentMove == cmailOldMove &&
13937             commentList[cmailOldMove] != NULL &&
13938             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13939                    "Black offers a draw" : "White offers a draw")) {
13940 #ifdef NOTDEF
13941             AppendComment(cmailOldMove, "Draw declined", TRUE);
13942             DisplayComment(cmailOldMove - 1, "Draw declined");
13943 #endif /*NOTDEF*/
13944         } else {
13945             DisplayError(_("There is no pending offer on this move"), 0);
13946         }
13947     } else {
13948         /* Not used for offers from chess program */
13949     }
13950 }
13951
13952 void
13953 RematchEvent ()
13954 {
13955     /* Issue ICS rematch command */
13956     if (appData.icsActive) {
13957         SendToICS(ics_prefix);
13958         SendToICS("rematch\n");
13959     }
13960 }
13961
13962 void
13963 CallFlagEvent ()
13964 {
13965     /* Call your opponent's flag (claim a win on time) */
13966     if (appData.icsActive) {
13967         SendToICS(ics_prefix);
13968         SendToICS("flag\n");
13969     } else {
13970         switch (gameMode) {
13971           default:
13972             return;
13973           case MachinePlaysWhite:
13974             if (whiteFlag) {
13975                 if (blackFlag)
13976                   GameEnds(GameIsDrawn, "Both players ran out of time",
13977                            GE_PLAYER);
13978                 else
13979                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13980             } else {
13981                 DisplayError(_("Your opponent is not out of time"), 0);
13982             }
13983             break;
13984           case MachinePlaysBlack:
13985             if (blackFlag) {
13986                 if (whiteFlag)
13987                   GameEnds(GameIsDrawn, "Both players ran out of time",
13988                            GE_PLAYER);
13989                 else
13990                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13991             } else {
13992                 DisplayError(_("Your opponent is not out of time"), 0);
13993             }
13994             break;
13995         }
13996     }
13997 }
13998
13999 void
14000 ClockClick (int which)
14001 {       // [HGM] code moved to back-end from winboard.c
14002         if(which) { // black clock
14003           if (gameMode == EditPosition || gameMode == IcsExamining) {
14004             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14005             SetBlackToPlayEvent();
14006           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14007           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14008           } else if (shiftKey) {
14009             AdjustClock(which, -1);
14010           } else if (gameMode == IcsPlayingWhite ||
14011                      gameMode == MachinePlaysBlack) {
14012             CallFlagEvent();
14013           }
14014         } else { // white clock
14015           if (gameMode == EditPosition || gameMode == IcsExamining) {
14016             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14017             SetWhiteToPlayEvent();
14018           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14019           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14020           } else if (shiftKey) {
14021             AdjustClock(which, -1);
14022           } else if (gameMode == IcsPlayingBlack ||
14023                    gameMode == MachinePlaysWhite) {
14024             CallFlagEvent();
14025           }
14026         }
14027 }
14028
14029 void
14030 DrawEvent ()
14031 {
14032     /* Offer draw or accept pending draw offer from opponent */
14033
14034     if (appData.icsActive) {
14035         /* Note: tournament rules require draw offers to be
14036            made after you make your move but before you punch
14037            your clock.  Currently ICS doesn't let you do that;
14038            instead, you immediately punch your clock after making
14039            a move, but you can offer a draw at any time. */
14040
14041         SendToICS(ics_prefix);
14042         SendToICS("draw\n");
14043         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14044     } else if (cmailMsgLoaded) {
14045         if (currentMove == cmailOldMove &&
14046             commentList[cmailOldMove] != NULL &&
14047             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14048                    "Black offers a draw" : "White offers a draw")) {
14049             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14050             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14051         } else if (currentMove == cmailOldMove + 1) {
14052             char *offer = WhiteOnMove(cmailOldMove) ?
14053               "White offers a draw" : "Black offers a draw";
14054             AppendComment(currentMove, offer, TRUE);
14055             DisplayComment(currentMove - 1, offer);
14056             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14057         } else {
14058             DisplayError(_("You must make your move before offering a draw"), 0);
14059             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14060         }
14061     } else if (first.offeredDraw) {
14062         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14063     } else {
14064         if (first.sendDrawOffers) {
14065             SendToProgram("draw\n", &first);
14066             userOfferedDraw = TRUE;
14067         }
14068     }
14069 }
14070
14071 void
14072 AdjournEvent ()
14073 {
14074     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14075
14076     if (appData.icsActive) {
14077         SendToICS(ics_prefix);
14078         SendToICS("adjourn\n");
14079     } else {
14080         /* Currently GNU Chess doesn't offer or accept Adjourns */
14081     }
14082 }
14083
14084
14085 void
14086 AbortEvent ()
14087 {
14088     /* Offer Abort or accept pending Abort offer from opponent */
14089
14090     if (appData.icsActive) {
14091         SendToICS(ics_prefix);
14092         SendToICS("abort\n");
14093     } else {
14094         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14095     }
14096 }
14097
14098 void
14099 ResignEvent ()
14100 {
14101     /* Resign.  You can do this even if it's not your turn. */
14102
14103     if (appData.icsActive) {
14104         SendToICS(ics_prefix);
14105         SendToICS("resign\n");
14106     } else {
14107         switch (gameMode) {
14108           case MachinePlaysWhite:
14109             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14110             break;
14111           case MachinePlaysBlack:
14112             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14113             break;
14114           case EditGame:
14115             if (cmailMsgLoaded) {
14116                 TruncateGame();
14117                 if (WhiteOnMove(cmailOldMove)) {
14118                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14119                 } else {
14120                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14121                 }
14122                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14123             }
14124             break;
14125           default:
14126             break;
14127         }
14128     }
14129 }
14130
14131
14132 void
14133 StopObservingEvent ()
14134 {
14135     /* Stop observing current games */
14136     SendToICS(ics_prefix);
14137     SendToICS("unobserve\n");
14138 }
14139
14140 void
14141 StopExaminingEvent ()
14142 {
14143     /* Stop observing current game */
14144     SendToICS(ics_prefix);
14145     SendToICS("unexamine\n");
14146 }
14147
14148 void
14149 ForwardInner (int target)
14150 {
14151     int limit;
14152
14153     if (appData.debugMode)
14154         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14155                 target, currentMove, forwardMostMove);
14156
14157     if (gameMode == EditPosition)
14158       return;
14159
14160     MarkTargetSquares(1);
14161
14162     if (gameMode == PlayFromGameFile && !pausing)
14163       PauseEvent();
14164
14165     if (gameMode == IcsExamining && pausing)
14166       limit = pauseExamForwardMostMove;
14167     else
14168       limit = forwardMostMove;
14169
14170     if (target > limit) target = limit;
14171
14172     if (target > 0 && moveList[target - 1][0]) {
14173         int fromX, fromY, toX, toY;
14174         toX = moveList[target - 1][2] - AAA;
14175         toY = moveList[target - 1][3] - ONE;
14176         if (moveList[target - 1][1] == '@') {
14177             if (appData.highlightLastMove) {
14178                 SetHighlights(-1, -1, toX, toY);
14179             }
14180         } else {
14181             fromX = moveList[target - 1][0] - AAA;
14182             fromY = moveList[target - 1][1] - ONE;
14183             if (target == currentMove + 1) {
14184                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14185             }
14186             if (appData.highlightLastMove) {
14187                 SetHighlights(fromX, fromY, toX, toY);
14188             }
14189         }
14190     }
14191     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14192         gameMode == Training || gameMode == PlayFromGameFile ||
14193         gameMode == AnalyzeFile) {
14194         while (currentMove < target) {
14195             SendMoveToProgram(currentMove++, &first);
14196         }
14197     } else {
14198         currentMove = target;
14199     }
14200
14201     if (gameMode == EditGame || gameMode == EndOfGame) {
14202         whiteTimeRemaining = timeRemaining[0][currentMove];
14203         blackTimeRemaining = timeRemaining[1][currentMove];
14204     }
14205     DisplayBothClocks();
14206     DisplayMove(currentMove - 1);
14207     DrawPosition(FALSE, boards[currentMove]);
14208     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14209     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14210         DisplayComment(currentMove - 1, commentList[currentMove]);
14211     }
14212 }
14213
14214
14215 void
14216 ForwardEvent ()
14217 {
14218     if (gameMode == IcsExamining && !pausing) {
14219         SendToICS(ics_prefix);
14220         SendToICS("forward\n");
14221     } else {
14222         ForwardInner(currentMove + 1);
14223     }
14224 }
14225
14226 void
14227 ToEndEvent ()
14228 {
14229     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14230         /* to optimze, we temporarily turn off analysis mode while we feed
14231          * the remaining moves to the engine. Otherwise we get analysis output
14232          * after each move.
14233          */
14234         if (first.analysisSupport) {
14235           SendToProgram("exit\nforce\n", &first);
14236           first.analyzing = FALSE;
14237         }
14238     }
14239
14240     if (gameMode == IcsExamining && !pausing) {
14241         SendToICS(ics_prefix);
14242         SendToICS("forward 999999\n");
14243     } else {
14244         ForwardInner(forwardMostMove);
14245     }
14246
14247     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14248         /* we have fed all the moves, so reactivate analysis mode */
14249         SendToProgram("analyze\n", &first);
14250         first.analyzing = TRUE;
14251         /*first.maybeThinking = TRUE;*/
14252         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14253     }
14254 }
14255
14256 void
14257 BackwardInner (int target)
14258 {
14259     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14260
14261     if (appData.debugMode)
14262         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14263                 target, currentMove, forwardMostMove);
14264
14265     if (gameMode == EditPosition) return;
14266     MarkTargetSquares(1);
14267     if (currentMove <= backwardMostMove) {
14268         ClearHighlights();
14269         DrawPosition(full_redraw, boards[currentMove]);
14270         return;
14271     }
14272     if (gameMode == PlayFromGameFile && !pausing)
14273       PauseEvent();
14274
14275     if (moveList[target][0]) {
14276         int fromX, fromY, toX, toY;
14277         toX = moveList[target][2] - AAA;
14278         toY = moveList[target][3] - ONE;
14279         if (moveList[target][1] == '@') {
14280             if (appData.highlightLastMove) {
14281                 SetHighlights(-1, -1, toX, toY);
14282             }
14283         } else {
14284             fromX = moveList[target][0] - AAA;
14285             fromY = moveList[target][1] - ONE;
14286             if (target == currentMove - 1) {
14287                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14288             }
14289             if (appData.highlightLastMove) {
14290                 SetHighlights(fromX, fromY, toX, toY);
14291             }
14292         }
14293     }
14294     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14295         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14296         while (currentMove > target) {
14297             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14298                 // null move cannot be undone. Reload program with move history before it.
14299                 int i;
14300                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14301                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14302                 }
14303                 SendBoard(&first, i); 
14304                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14305                 break;
14306             }
14307             SendToProgram("undo\n", &first);
14308             currentMove--;
14309         }
14310     } else {
14311         currentMove = target;
14312     }
14313
14314     if (gameMode == EditGame || gameMode == EndOfGame) {
14315         whiteTimeRemaining = timeRemaining[0][currentMove];
14316         blackTimeRemaining = timeRemaining[1][currentMove];
14317     }
14318     DisplayBothClocks();
14319     DisplayMove(currentMove - 1);
14320     DrawPosition(full_redraw, boards[currentMove]);
14321     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14322     // [HGM] PV info: routine tests if comment empty
14323     DisplayComment(currentMove - 1, commentList[currentMove]);
14324 }
14325
14326 void
14327 BackwardEvent ()
14328 {
14329     if (gameMode == IcsExamining && !pausing) {
14330         SendToICS(ics_prefix);
14331         SendToICS("backward\n");
14332     } else {
14333         BackwardInner(currentMove - 1);
14334     }
14335 }
14336
14337 void
14338 ToStartEvent ()
14339 {
14340     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14341         /* to optimize, we temporarily turn off analysis mode while we undo
14342          * all the moves. Otherwise we get analysis output after each undo.
14343          */
14344         if (first.analysisSupport) {
14345           SendToProgram("exit\nforce\n", &first);
14346           first.analyzing = FALSE;
14347         }
14348     }
14349
14350     if (gameMode == IcsExamining && !pausing) {
14351         SendToICS(ics_prefix);
14352         SendToICS("backward 999999\n");
14353     } else {
14354         BackwardInner(backwardMostMove);
14355     }
14356
14357     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14358         /* we have fed all the moves, so reactivate analysis mode */
14359         SendToProgram("analyze\n", &first);
14360         first.analyzing = TRUE;
14361         /*first.maybeThinking = TRUE;*/
14362         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14363     }
14364 }
14365
14366 void
14367 ToNrEvent (int to)
14368 {
14369   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14370   if (to >= forwardMostMove) to = forwardMostMove;
14371   if (to <= backwardMostMove) to = backwardMostMove;
14372   if (to < currentMove) {
14373     BackwardInner(to);
14374   } else {
14375     ForwardInner(to);
14376   }
14377 }
14378
14379 void
14380 RevertEvent (Boolean annotate)
14381 {
14382     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14383         return;
14384     }
14385     if (gameMode != IcsExamining) {
14386         DisplayError(_("You are not examining a game"), 0);
14387         return;
14388     }
14389     if (pausing) {
14390         DisplayError(_("You can't revert while pausing"), 0);
14391         return;
14392     }
14393     SendToICS(ics_prefix);
14394     SendToICS("revert\n");
14395 }
14396
14397 void
14398 RetractMoveEvent ()
14399 {
14400     switch (gameMode) {
14401       case MachinePlaysWhite:
14402       case MachinePlaysBlack:
14403         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14404             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14405             return;
14406         }
14407         if (forwardMostMove < 2) return;
14408         currentMove = forwardMostMove = forwardMostMove - 2;
14409         whiteTimeRemaining = timeRemaining[0][currentMove];
14410         blackTimeRemaining = timeRemaining[1][currentMove];
14411         DisplayBothClocks();
14412         DisplayMove(currentMove - 1);
14413         ClearHighlights();/*!! could figure this out*/
14414         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14415         SendToProgram("remove\n", &first);
14416         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14417         break;
14418
14419       case BeginningOfGame:
14420       default:
14421         break;
14422
14423       case IcsPlayingWhite:
14424       case IcsPlayingBlack:
14425         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14426             SendToICS(ics_prefix);
14427             SendToICS("takeback 2\n");
14428         } else {
14429             SendToICS(ics_prefix);
14430             SendToICS("takeback 1\n");
14431         }
14432         break;
14433     }
14434 }
14435
14436 void
14437 MoveNowEvent ()
14438 {
14439     ChessProgramState *cps;
14440
14441     switch (gameMode) {
14442       case MachinePlaysWhite:
14443         if (!WhiteOnMove(forwardMostMove)) {
14444             DisplayError(_("It is your turn"), 0);
14445             return;
14446         }
14447         cps = &first;
14448         break;
14449       case MachinePlaysBlack:
14450         if (WhiteOnMove(forwardMostMove)) {
14451             DisplayError(_("It is your turn"), 0);
14452             return;
14453         }
14454         cps = &first;
14455         break;
14456       case TwoMachinesPlay:
14457         if (WhiteOnMove(forwardMostMove) ==
14458             (first.twoMachinesColor[0] == 'w')) {
14459             cps = &first;
14460         } else {
14461             cps = &second;
14462         }
14463         break;
14464       case BeginningOfGame:
14465       default:
14466         return;
14467     }
14468     SendToProgram("?\n", cps);
14469 }
14470
14471 void
14472 TruncateGameEvent ()
14473 {
14474     EditGameEvent();
14475     if (gameMode != EditGame) return;
14476     TruncateGame();
14477 }
14478
14479 void
14480 TruncateGame ()
14481 {
14482     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14483     if (forwardMostMove > currentMove) {
14484         if (gameInfo.resultDetails != NULL) {
14485             free(gameInfo.resultDetails);
14486             gameInfo.resultDetails = NULL;
14487             gameInfo.result = GameUnfinished;
14488         }
14489         forwardMostMove = currentMove;
14490         HistorySet(parseList, backwardMostMove, forwardMostMove,
14491                    currentMove-1);
14492     }
14493 }
14494
14495 void
14496 HintEvent ()
14497 {
14498     if (appData.noChessProgram) return;
14499     switch (gameMode) {
14500       case MachinePlaysWhite:
14501         if (WhiteOnMove(forwardMostMove)) {
14502             DisplayError(_("Wait until your turn"), 0);
14503             return;
14504         }
14505         break;
14506       case BeginningOfGame:
14507       case MachinePlaysBlack:
14508         if (!WhiteOnMove(forwardMostMove)) {
14509             DisplayError(_("Wait until your turn"), 0);
14510             return;
14511         }
14512         break;
14513       default:
14514         DisplayError(_("No hint available"), 0);
14515         return;
14516     }
14517     SendToProgram("hint\n", &first);
14518     hintRequested = TRUE;
14519 }
14520
14521 void
14522 BookEvent ()
14523 {
14524     if (appData.noChessProgram) return;
14525     switch (gameMode) {
14526       case MachinePlaysWhite:
14527         if (WhiteOnMove(forwardMostMove)) {
14528             DisplayError(_("Wait until your turn"), 0);
14529             return;
14530         }
14531         break;
14532       case BeginningOfGame:
14533       case MachinePlaysBlack:
14534         if (!WhiteOnMove(forwardMostMove)) {
14535             DisplayError(_("Wait until your turn"), 0);
14536             return;
14537         }
14538         break;
14539       case EditPosition:
14540         EditPositionDone(TRUE);
14541         break;
14542       case TwoMachinesPlay:
14543         return;
14544       default:
14545         break;
14546     }
14547     SendToProgram("bk\n", &first);
14548     bookOutput[0] = NULLCHAR;
14549     bookRequested = TRUE;
14550 }
14551
14552 void
14553 AboutGameEvent ()
14554 {
14555     char *tags = PGNTags(&gameInfo);
14556     TagsPopUp(tags, CmailMsg());
14557     free(tags);
14558 }
14559
14560 /* end button procedures */
14561
14562 void
14563 PrintPosition (FILE *fp, int move)
14564 {
14565     int i, j;
14566
14567     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14568         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14569             char c = PieceToChar(boards[move][i][j]);
14570             fputc(c == 'x' ? '.' : c, fp);
14571             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14572         }
14573     }
14574     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14575       fprintf(fp, "white to play\n");
14576     else
14577       fprintf(fp, "black to play\n");
14578 }
14579
14580 void
14581 PrintOpponents (FILE *fp)
14582 {
14583     if (gameInfo.white != NULL) {
14584         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14585     } else {
14586         fprintf(fp, "\n");
14587     }
14588 }
14589
14590 /* Find last component of program's own name, using some heuristics */
14591 void
14592 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14593 {
14594     char *p, *q;
14595     int local = (strcmp(host, "localhost") == 0);
14596     while (!local && (p = strchr(prog, ';')) != NULL) {
14597         p++;
14598         while (*p == ' ') p++;
14599         prog = p;
14600     }
14601     if (*prog == '"' || *prog == '\'') {
14602         q = strchr(prog + 1, *prog);
14603     } else {
14604         q = strchr(prog, ' ');
14605     }
14606     if (q == NULL) q = prog + strlen(prog);
14607     p = q;
14608     while (p >= prog && *p != '/' && *p != '\\') p--;
14609     p++;
14610     if(p == prog && *p == '"') p++;
14611     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14612     memcpy(buf, p, q - p);
14613     buf[q - p] = NULLCHAR;
14614     if (!local) {
14615         strcat(buf, "@");
14616         strcat(buf, host);
14617     }
14618 }
14619
14620 char *
14621 TimeControlTagValue ()
14622 {
14623     char buf[MSG_SIZ];
14624     if (!appData.clockMode) {
14625       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14626     } else if (movesPerSession > 0) {
14627       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14628     } else if (timeIncrement == 0) {
14629       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14630     } else {
14631       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14632     }
14633     return StrSave(buf);
14634 }
14635
14636 void
14637 SetGameInfo ()
14638 {
14639     /* This routine is used only for certain modes */
14640     VariantClass v = gameInfo.variant;
14641     ChessMove r = GameUnfinished;
14642     char *p = NULL;
14643
14644     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14645         r = gameInfo.result;
14646         p = gameInfo.resultDetails;
14647         gameInfo.resultDetails = NULL;
14648     }
14649     ClearGameInfo(&gameInfo);
14650     gameInfo.variant = v;
14651
14652     switch (gameMode) {
14653       case MachinePlaysWhite:
14654         gameInfo.event = StrSave( appData.pgnEventHeader );
14655         gameInfo.site = StrSave(HostName());
14656         gameInfo.date = PGNDate();
14657         gameInfo.round = StrSave("-");
14658         gameInfo.white = StrSave(first.tidy);
14659         gameInfo.black = StrSave(UserName());
14660         gameInfo.timeControl = TimeControlTagValue();
14661         break;
14662
14663       case MachinePlaysBlack:
14664         gameInfo.event = StrSave( appData.pgnEventHeader );
14665         gameInfo.site = StrSave(HostName());
14666         gameInfo.date = PGNDate();
14667         gameInfo.round = StrSave("-");
14668         gameInfo.white = StrSave(UserName());
14669         gameInfo.black = StrSave(first.tidy);
14670         gameInfo.timeControl = TimeControlTagValue();
14671         break;
14672
14673       case TwoMachinesPlay:
14674         gameInfo.event = StrSave( appData.pgnEventHeader );
14675         gameInfo.site = StrSave(HostName());
14676         gameInfo.date = PGNDate();
14677         if (roundNr > 0) {
14678             char buf[MSG_SIZ];
14679             snprintf(buf, MSG_SIZ, "%d", roundNr);
14680             gameInfo.round = StrSave(buf);
14681         } else {
14682             gameInfo.round = StrSave("-");
14683         }
14684         if (first.twoMachinesColor[0] == 'w') {
14685             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14686             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14687         } else {
14688             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14689             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14690         }
14691         gameInfo.timeControl = TimeControlTagValue();
14692         break;
14693
14694       case EditGame:
14695         gameInfo.event = StrSave("Edited game");
14696         gameInfo.site = StrSave(HostName());
14697         gameInfo.date = PGNDate();
14698         gameInfo.round = StrSave("-");
14699         gameInfo.white = StrSave("-");
14700         gameInfo.black = StrSave("-");
14701         gameInfo.result = r;
14702         gameInfo.resultDetails = p;
14703         break;
14704
14705       case EditPosition:
14706         gameInfo.event = StrSave("Edited position");
14707         gameInfo.site = StrSave(HostName());
14708         gameInfo.date = PGNDate();
14709         gameInfo.round = StrSave("-");
14710         gameInfo.white = StrSave("-");
14711         gameInfo.black = StrSave("-");
14712         break;
14713
14714       case IcsPlayingWhite:
14715       case IcsPlayingBlack:
14716       case IcsObserving:
14717       case IcsExamining:
14718         break;
14719
14720       case PlayFromGameFile:
14721         gameInfo.event = StrSave("Game from non-PGN file");
14722         gameInfo.site = StrSave(HostName());
14723         gameInfo.date = PGNDate();
14724         gameInfo.round = StrSave("-");
14725         gameInfo.white = StrSave("?");
14726         gameInfo.black = StrSave("?");
14727         break;
14728
14729       default:
14730         break;
14731     }
14732 }
14733
14734 void
14735 ReplaceComment (int index, char *text)
14736 {
14737     int len;
14738     char *p;
14739     float score;
14740
14741     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14742        pvInfoList[index-1].depth == len &&
14743        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14744        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14745     while (*text == '\n') text++;
14746     len = strlen(text);
14747     while (len > 0 && text[len - 1] == '\n') len--;
14748
14749     if (commentList[index] != NULL)
14750       free(commentList[index]);
14751
14752     if (len == 0) {
14753         commentList[index] = NULL;
14754         return;
14755     }
14756   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14757       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14758       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14759     commentList[index] = (char *) malloc(len + 2);
14760     strncpy(commentList[index], text, len);
14761     commentList[index][len] = '\n';
14762     commentList[index][len + 1] = NULLCHAR;
14763   } else {
14764     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14765     char *p;
14766     commentList[index] = (char *) malloc(len + 7);
14767     safeStrCpy(commentList[index], "{\n", 3);
14768     safeStrCpy(commentList[index]+2, text, len+1);
14769     commentList[index][len+2] = NULLCHAR;
14770     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14771     strcat(commentList[index], "\n}\n");
14772   }
14773 }
14774
14775 void
14776 CrushCRs (char *text)
14777 {
14778   char *p = text;
14779   char *q = text;
14780   char ch;
14781
14782   do {
14783     ch = *p++;
14784     if (ch == '\r') continue;
14785     *q++ = ch;
14786   } while (ch != '\0');
14787 }
14788
14789 void
14790 AppendComment (int index, char *text, Boolean addBraces)
14791 /* addBraces  tells if we should add {} */
14792 {
14793     int oldlen, len;
14794     char *old;
14795
14796 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14797     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14798
14799     CrushCRs(text);
14800     while (*text == '\n') text++;
14801     len = strlen(text);
14802     while (len > 0 && text[len - 1] == '\n') len--;
14803     text[len] = NULLCHAR;
14804
14805     if (len == 0) return;
14806
14807     if (commentList[index] != NULL) {
14808       Boolean addClosingBrace = addBraces;
14809         old = commentList[index];
14810         oldlen = strlen(old);
14811         while(commentList[index][oldlen-1] ==  '\n')
14812           commentList[index][--oldlen] = NULLCHAR;
14813         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14814         safeStrCpy(commentList[index], old, oldlen + len + 6);
14815         free(old);
14816         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14817         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14818           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14819           while (*text == '\n') { text++; len--; }
14820           commentList[index][--oldlen] = NULLCHAR;
14821       }
14822         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14823         else          strcat(commentList[index], "\n");
14824         strcat(commentList[index], text);
14825         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14826         else          strcat(commentList[index], "\n");
14827     } else {
14828         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14829         if(addBraces)
14830           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14831         else commentList[index][0] = NULLCHAR;
14832         strcat(commentList[index], text);
14833         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14834         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14835     }
14836 }
14837
14838 static char *
14839 FindStr (char * text, char * sub_text)
14840 {
14841     char * result = strstr( text, sub_text );
14842
14843     if( result != NULL ) {
14844         result += strlen( sub_text );
14845     }
14846
14847     return result;
14848 }
14849
14850 /* [AS] Try to extract PV info from PGN comment */
14851 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14852 char *
14853 GetInfoFromComment (int index, char * text)
14854 {
14855     char * sep = text, *p;
14856
14857     if( text != NULL && index > 0 ) {
14858         int score = 0;
14859         int depth = 0;
14860         int time = -1, sec = 0, deci;
14861         char * s_eval = FindStr( text, "[%eval " );
14862         char * s_emt = FindStr( text, "[%emt " );
14863
14864         if( s_eval != NULL || s_emt != NULL ) {
14865             /* New style */
14866             char delim;
14867
14868             if( s_eval != NULL ) {
14869                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14870                     return text;
14871                 }
14872
14873                 if( delim != ']' ) {
14874                     return text;
14875                 }
14876             }
14877
14878             if( s_emt != NULL ) {
14879             }
14880                 return text;
14881         }
14882         else {
14883             /* We expect something like: [+|-]nnn.nn/dd */
14884             int score_lo = 0;
14885
14886             if(*text != '{') return text; // [HGM] braces: must be normal comment
14887
14888             sep = strchr( text, '/' );
14889             if( sep == NULL || sep < (text+4) ) {
14890                 return text;
14891             }
14892
14893             p = text;
14894             if(p[1] == '(') { // comment starts with PV
14895                p = strchr(p, ')'); // locate end of PV
14896                if(p == NULL || sep < p+5) return text;
14897                // at this point we have something like "{(.*) +0.23/6 ..."
14898                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14899                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14900                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14901             }
14902             time = -1; sec = -1; deci = -1;
14903             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14904                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14905                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14906                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14907                 return text;
14908             }
14909
14910             if( score_lo < 0 || score_lo >= 100 ) {
14911                 return text;
14912             }
14913
14914             if(sec >= 0) time = 600*time + 10*sec; else
14915             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14916
14917             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14918
14919             /* [HGM] PV time: now locate end of PV info */
14920             while( *++sep >= '0' && *sep <= '9'); // strip depth
14921             if(time >= 0)
14922             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14923             if(sec >= 0)
14924             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14925             if(deci >= 0)
14926             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14927             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
14928         }
14929
14930         if( depth <= 0 ) {
14931             return text;
14932         }
14933
14934         if( time < 0 ) {
14935             time = -1;
14936         }
14937
14938         pvInfoList[index-1].depth = depth;
14939         pvInfoList[index-1].score = score;
14940         pvInfoList[index-1].time  = 10*time; // centi-sec
14941         if(*sep == '}') *sep = 0; else *--sep = '{';
14942         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14943     }
14944     return sep;
14945 }
14946
14947 void
14948 SendToProgram (char *message, ChessProgramState *cps)
14949 {
14950     int count, outCount, error;
14951     char buf[MSG_SIZ];
14952
14953     if (cps->pr == NoProc) return;
14954     Attention(cps);
14955
14956     if (appData.debugMode) {
14957         TimeMark now;
14958         GetTimeMark(&now);
14959         fprintf(debugFP, "%ld >%-6s: %s",
14960                 SubtractTimeMarks(&now, &programStartTime),
14961                 cps->which, message);
14962     }
14963
14964     count = strlen(message);
14965     outCount = OutputToProcess(cps->pr, message, count, &error);
14966     if (outCount < count && !exiting
14967                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14968       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14969       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14970         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14971             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14972                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14973                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14974                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14975             } else {
14976                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14977                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14978                 gameInfo.result = res;
14979             }
14980             gameInfo.resultDetails = StrSave(buf);
14981         }
14982         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14983         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14984     }
14985 }
14986
14987 void
14988 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
14989 {
14990     char *end_str;
14991     char buf[MSG_SIZ];
14992     ChessProgramState *cps = (ChessProgramState *)closure;
14993
14994     if (isr != cps->isr) return; /* Killed intentionally */
14995     if (count <= 0) {
14996         if (count == 0) {
14997             RemoveInputSource(cps->isr);
14998             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14999             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15000                     _(cps->which), cps->program);
15001         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15002                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15003                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15004                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15005                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15006                 } else {
15007                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15008                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15009                     gameInfo.result = res;
15010                 }
15011                 gameInfo.resultDetails = StrSave(buf);
15012             }
15013             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15014             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15015         } else {
15016             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15017                     _(cps->which), cps->program);
15018             RemoveInputSource(cps->isr);
15019
15020             /* [AS] Program is misbehaving badly... kill it */
15021             if( count == -2 ) {
15022                 DestroyChildProcess( cps->pr, 9 );
15023                 cps->pr = NoProc;
15024             }
15025
15026             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15027         }
15028         return;
15029     }
15030
15031     if ((end_str = strchr(message, '\r')) != NULL)
15032       *end_str = NULLCHAR;
15033     if ((end_str = strchr(message, '\n')) != NULL)
15034       *end_str = NULLCHAR;
15035
15036     if (appData.debugMode) {
15037         TimeMark now; int print = 1;
15038         char *quote = ""; char c; int i;
15039
15040         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15041                 char start = message[0];
15042                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15043                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15044                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15045                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15046                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15047                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15048                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15049                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15050                    sscanf(message, "hint: %c", &c)!=1 && 
15051                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15052                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15053                     print = (appData.engineComments >= 2);
15054                 }
15055                 message[0] = start; // restore original message
15056         }
15057         if(print) {
15058                 GetTimeMark(&now);
15059                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15060                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15061                         quote,
15062                         message);
15063         }
15064     }
15065
15066     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15067     if (appData.icsEngineAnalyze) {
15068         if (strstr(message, "whisper") != NULL ||
15069              strstr(message, "kibitz") != NULL ||
15070             strstr(message, "tellics") != NULL) return;
15071     }
15072
15073     HandleMachineMove(message, cps);
15074 }
15075
15076
15077 void
15078 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15079 {
15080     char buf[MSG_SIZ];
15081     int seconds;
15082
15083     if( timeControl_2 > 0 ) {
15084         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15085             tc = timeControl_2;
15086         }
15087     }
15088     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15089     inc /= cps->timeOdds;
15090     st  /= cps->timeOdds;
15091
15092     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15093
15094     if (st > 0) {
15095       /* Set exact time per move, normally using st command */
15096       if (cps->stKludge) {
15097         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15098         seconds = st % 60;
15099         if (seconds == 0) {
15100           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15101         } else {
15102           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15103         }
15104       } else {
15105         snprintf(buf, MSG_SIZ, "st %d\n", st);
15106       }
15107     } else {
15108       /* Set conventional or incremental time control, using level command */
15109       if (seconds == 0) {
15110         /* Note old gnuchess bug -- minutes:seconds used to not work.
15111            Fixed in later versions, but still avoid :seconds
15112            when seconds is 0. */
15113         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15114       } else {
15115         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15116                  seconds, inc/1000.);
15117       }
15118     }
15119     SendToProgram(buf, cps);
15120
15121     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15122     /* Orthogonally, limit search to given depth */
15123     if (sd > 0) {
15124       if (cps->sdKludge) {
15125         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15126       } else {
15127         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15128       }
15129       SendToProgram(buf, cps);
15130     }
15131
15132     if(cps->nps >= 0) { /* [HGM] nps */
15133         if(cps->supportsNPS == FALSE)
15134           cps->nps = -1; // don't use if engine explicitly says not supported!
15135         else {
15136           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15137           SendToProgram(buf, cps);
15138         }
15139     }
15140 }
15141
15142 ChessProgramState *
15143 WhitePlayer ()
15144 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15145 {
15146     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15147        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15148         return &second;
15149     return &first;
15150 }
15151
15152 void
15153 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15154 {
15155     char message[MSG_SIZ];
15156     long time, otime;
15157
15158     /* Note: this routine must be called when the clocks are stopped
15159        or when they have *just* been set or switched; otherwise
15160        it will be off by the time since the current tick started.
15161     */
15162     if (machineWhite) {
15163         time = whiteTimeRemaining / 10;
15164         otime = blackTimeRemaining / 10;
15165     } else {
15166         time = blackTimeRemaining / 10;
15167         otime = whiteTimeRemaining / 10;
15168     }
15169     /* [HGM] translate opponent's time by time-odds factor */
15170     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15171     if (appData.debugMode) {
15172         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15173     }
15174
15175     if (time <= 0) time = 1;
15176     if (otime <= 0) otime = 1;
15177
15178     snprintf(message, MSG_SIZ, "time %ld\n", time);
15179     SendToProgram(message, cps);
15180
15181     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15182     SendToProgram(message, cps);
15183 }
15184
15185 int
15186 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15187 {
15188   char buf[MSG_SIZ];
15189   int len = strlen(name);
15190   int val;
15191
15192   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15193     (*p) += len + 1;
15194     sscanf(*p, "%d", &val);
15195     *loc = (val != 0);
15196     while (**p && **p != ' ')
15197       (*p)++;
15198     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15199     SendToProgram(buf, cps);
15200     return TRUE;
15201   }
15202   return FALSE;
15203 }
15204
15205 int
15206 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15207 {
15208   char buf[MSG_SIZ];
15209   int len = strlen(name);
15210   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15211     (*p) += len + 1;
15212     sscanf(*p, "%d", loc);
15213     while (**p && **p != ' ') (*p)++;
15214     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15215     SendToProgram(buf, cps);
15216     return TRUE;
15217   }
15218   return FALSE;
15219 }
15220
15221 int
15222 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15223 {
15224   char buf[MSG_SIZ];
15225   int len = strlen(name);
15226   if (strncmp((*p), name, len) == 0
15227       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15228     (*p) += len + 2;
15229     sscanf(*p, "%[^\"]", loc);
15230     while (**p && **p != '\"') (*p)++;
15231     if (**p == '\"') (*p)++;
15232     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15233     SendToProgram(buf, cps);
15234     return TRUE;
15235   }
15236   return FALSE;
15237 }
15238
15239 int
15240 ParseOption (Option *opt, ChessProgramState *cps)
15241 // [HGM] options: process the string that defines an engine option, and determine
15242 // name, type, default value, and allowed value range
15243 {
15244         char *p, *q, buf[MSG_SIZ];
15245         int n, min = (-1)<<31, max = 1<<31, def;
15246
15247         if(p = strstr(opt->name, " -spin ")) {
15248             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15249             if(max < min) max = min; // enforce consistency
15250             if(def < min) def = min;
15251             if(def > max) def = max;
15252             opt->value = def;
15253             opt->min = min;
15254             opt->max = max;
15255             opt->type = Spin;
15256         } else if((p = strstr(opt->name, " -slider "))) {
15257             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15258             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15259             if(max < min) max = min; // enforce consistency
15260             if(def < min) def = min;
15261             if(def > max) def = max;
15262             opt->value = def;
15263             opt->min = min;
15264             opt->max = max;
15265             opt->type = Spin; // Slider;
15266         } else if((p = strstr(opt->name, " -string "))) {
15267             opt->textValue = p+9;
15268             opt->type = TextBox;
15269         } else if((p = strstr(opt->name, " -file "))) {
15270             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15271             opt->textValue = p+7;
15272             opt->type = FileName; // FileName;
15273         } else if((p = strstr(opt->name, " -path "))) {
15274             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15275             opt->textValue = p+7;
15276             opt->type = PathName; // PathName;
15277         } else if(p = strstr(opt->name, " -check ")) {
15278             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15279             opt->value = (def != 0);
15280             opt->type = CheckBox;
15281         } else if(p = strstr(opt->name, " -combo ")) {
15282             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15283             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15284             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15285             opt->value = n = 0;
15286             while(q = StrStr(q, " /// ")) {
15287                 n++; *q = 0;    // count choices, and null-terminate each of them
15288                 q += 5;
15289                 if(*q == '*') { // remember default, which is marked with * prefix
15290                     q++;
15291                     opt->value = n;
15292                 }
15293                 cps->comboList[cps->comboCnt++] = q;
15294             }
15295             cps->comboList[cps->comboCnt++] = NULL;
15296             opt->max = n + 1;
15297             opt->type = ComboBox;
15298         } else if(p = strstr(opt->name, " -button")) {
15299             opt->type = Button;
15300         } else if(p = strstr(opt->name, " -save")) {
15301             opt->type = SaveButton;
15302         } else return FALSE;
15303         *p = 0; // terminate option name
15304         // now look if the command-line options define a setting for this engine option.
15305         if(cps->optionSettings && cps->optionSettings[0])
15306             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15307         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15308           snprintf(buf, MSG_SIZ, "option %s", p);
15309                 if(p = strstr(buf, ",")) *p = 0;
15310                 if(q = strchr(buf, '=')) switch(opt->type) {
15311                     case ComboBox:
15312                         for(n=0; n<opt->max; n++)
15313                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15314                         break;
15315                     case TextBox:
15316                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15317                         break;
15318                     case Spin:
15319                     case CheckBox:
15320                         opt->value = atoi(q+1);
15321                     default:
15322                         break;
15323                 }
15324                 strcat(buf, "\n");
15325                 SendToProgram(buf, cps);
15326         }
15327         return TRUE;
15328 }
15329
15330 void
15331 FeatureDone (ChessProgramState *cps, int val)
15332 {
15333   DelayedEventCallback cb = GetDelayedEvent();
15334   if ((cb == InitBackEnd3 && cps == &first) ||
15335       (cb == SettingsMenuIfReady && cps == &second) ||
15336       (cb == LoadEngine) ||
15337       (cb == TwoMachinesEventIfReady)) {
15338     CancelDelayedEvent();
15339     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15340   }
15341   cps->initDone = val;
15342 }
15343
15344 /* Parse feature command from engine */
15345 void
15346 ParseFeatures (char *args, ChessProgramState *cps)
15347 {
15348   char *p = args;
15349   char *q;
15350   int val;
15351   char buf[MSG_SIZ];
15352
15353   for (;;) {
15354     while (*p == ' ') p++;
15355     if (*p == NULLCHAR) return;
15356
15357     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15358     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15359     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15360     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15361     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15362     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15363     if (BoolFeature(&p, "reuse", &val, cps)) {
15364       /* Engine can disable reuse, but can't enable it if user said no */
15365       if (!val) cps->reuse = FALSE;
15366       continue;
15367     }
15368     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15369     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
15370       if (gameMode == TwoMachinesPlay) {
15371         DisplayTwoMachinesTitle();
15372       } else {
15373         DisplayTitle("");
15374       }
15375       continue;
15376     }
15377     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
15378     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15379     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15380     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15381     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15382     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15383     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15384     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15385     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15386     if (IntFeature(&p, "done", &val, cps)) {
15387       FeatureDone(cps, val);
15388       continue;
15389     }
15390     /* Added by Tord: */
15391     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15392     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15393     /* End of additions by Tord */
15394
15395     /* [HGM] added features: */
15396     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15397     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15398     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15399     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15400     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15401     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
15402     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
15403         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15404           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15405             SendToProgram(buf, cps);
15406             continue;
15407         }
15408         if(cps->nrOptions >= MAX_OPTIONS) {
15409             cps->nrOptions--;
15410             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15411             DisplayError(buf, 0);
15412         }
15413         continue;
15414     }
15415     /* End of additions by HGM */
15416
15417     /* unknown feature: complain and skip */
15418     q = p;
15419     while (*q && *q != '=') q++;
15420     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15421     SendToProgram(buf, cps);
15422     p = q;
15423     if (*p == '=') {
15424       p++;
15425       if (*p == '\"') {
15426         p++;
15427         while (*p && *p != '\"') p++;
15428         if (*p == '\"') p++;
15429       } else {
15430         while (*p && *p != ' ') p++;
15431       }
15432     }
15433   }
15434
15435 }
15436
15437 void
15438 PeriodicUpdatesEvent (int newState)
15439 {
15440     if (newState == appData.periodicUpdates)
15441       return;
15442
15443     appData.periodicUpdates=newState;
15444
15445     /* Display type changes, so update it now */
15446 //    DisplayAnalysis();
15447
15448     /* Get the ball rolling again... */
15449     if (newState) {
15450         AnalysisPeriodicEvent(1);
15451         StartAnalysisClock();
15452     }
15453 }
15454
15455 void
15456 PonderNextMoveEvent (int newState)
15457 {
15458     if (newState == appData.ponderNextMove) return;
15459     if (gameMode == EditPosition) EditPositionDone(TRUE);
15460     if (newState) {
15461         SendToProgram("hard\n", &first);
15462         if (gameMode == TwoMachinesPlay) {
15463             SendToProgram("hard\n", &second);
15464         }
15465     } else {
15466         SendToProgram("easy\n", &first);
15467         thinkOutput[0] = NULLCHAR;
15468         if (gameMode == TwoMachinesPlay) {
15469             SendToProgram("easy\n", &second);
15470         }
15471     }
15472     appData.ponderNextMove = newState;
15473 }
15474
15475 void
15476 NewSettingEvent (int option, int *feature, char *command, int value)
15477 {
15478     char buf[MSG_SIZ];
15479
15480     if (gameMode == EditPosition) EditPositionDone(TRUE);
15481     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15482     if(feature == NULL || *feature) SendToProgram(buf, &first);
15483     if (gameMode == TwoMachinesPlay) {
15484         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15485     }
15486 }
15487
15488 void
15489 ShowThinkingEvent ()
15490 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15491 {
15492     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15493     int newState = appData.showThinking
15494         // [HGM] thinking: other features now need thinking output as well
15495         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15496
15497     if (oldState == newState) return;
15498     oldState = newState;
15499     if (gameMode == EditPosition) EditPositionDone(TRUE);
15500     if (oldState) {
15501         SendToProgram("post\n", &first);
15502         if (gameMode == TwoMachinesPlay) {
15503             SendToProgram("post\n", &second);
15504         }
15505     } else {
15506         SendToProgram("nopost\n", &first);
15507         thinkOutput[0] = NULLCHAR;
15508         if (gameMode == TwoMachinesPlay) {
15509             SendToProgram("nopost\n", &second);
15510         }
15511     }
15512 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15513 }
15514
15515 void
15516 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15517 {
15518   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15519   if (pr == NoProc) return;
15520   AskQuestion(title, question, replyPrefix, pr);
15521 }
15522
15523 void
15524 TypeInEvent (char firstChar)
15525 {
15526     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15527         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15528         gameMode == AnalyzeMode || gameMode == EditGame || 
15529         gameMode == EditPosition || gameMode == IcsExamining ||
15530         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15531         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15532                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15533                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15534         gameMode == Training) PopUpMoveDialog(firstChar);
15535 }
15536
15537 void
15538 TypeInDoneEvent (char *move)
15539 {
15540         Board board;
15541         int n, fromX, fromY, toX, toY;
15542         char promoChar;
15543         ChessMove moveType;
15544
15545         // [HGM] FENedit
15546         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15547                 EditPositionPasteFEN(move);
15548                 return;
15549         }
15550         // [HGM] movenum: allow move number to be typed in any mode
15551         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15552           ToNrEvent(2*n-1);
15553           return;
15554         }
15555
15556       if (gameMode != EditGame && currentMove != forwardMostMove && 
15557         gameMode != Training) {
15558         DisplayMoveError(_("Displayed move is not current"));
15559       } else {
15560         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15561           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15562         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15563         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15564           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15565           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15566         } else {
15567           DisplayMoveError(_("Could not parse move"));
15568         }
15569       }
15570 }
15571
15572 void
15573 DisplayMove (int moveNumber)
15574 {
15575     char message[MSG_SIZ];
15576     char res[MSG_SIZ];
15577     char cpThinkOutput[MSG_SIZ];
15578
15579     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15580
15581     if (moveNumber == forwardMostMove - 1 ||
15582         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15583
15584         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15585
15586         if (strchr(cpThinkOutput, '\n')) {
15587             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15588         }
15589     } else {
15590         *cpThinkOutput = NULLCHAR;
15591     }
15592
15593     /* [AS] Hide thinking from human user */
15594     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15595         *cpThinkOutput = NULLCHAR;
15596         if( thinkOutput[0] != NULLCHAR ) {
15597             int i;
15598
15599             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15600                 cpThinkOutput[i] = '.';
15601             }
15602             cpThinkOutput[i] = NULLCHAR;
15603             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15604         }
15605     }
15606
15607     if (moveNumber == forwardMostMove - 1 &&
15608         gameInfo.resultDetails != NULL) {
15609         if (gameInfo.resultDetails[0] == NULLCHAR) {
15610           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15611         } else {
15612           snprintf(res, MSG_SIZ, " {%s} %s",
15613                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15614         }
15615     } else {
15616         res[0] = NULLCHAR;
15617     }
15618
15619     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15620         DisplayMessage(res, cpThinkOutput);
15621     } else {
15622       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15623                 WhiteOnMove(moveNumber) ? " " : ".. ",
15624                 parseList[moveNumber], res);
15625         DisplayMessage(message, cpThinkOutput);
15626     }
15627 }
15628
15629 void
15630 DisplayComment (int moveNumber, char *text)
15631 {
15632     char title[MSG_SIZ];
15633
15634     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15635       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15636     } else {
15637       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15638               WhiteOnMove(moveNumber) ? " " : ".. ",
15639               parseList[moveNumber]);
15640     }
15641     if (text != NULL && (appData.autoDisplayComment || commentUp))
15642         CommentPopUp(title, text);
15643 }
15644
15645 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15646  * might be busy thinking or pondering.  It can be omitted if your
15647  * gnuchess is configured to stop thinking immediately on any user
15648  * input.  However, that gnuchess feature depends on the FIONREAD
15649  * ioctl, which does not work properly on some flavors of Unix.
15650  */
15651 void
15652 Attention (ChessProgramState *cps)
15653 {
15654 #if ATTENTION
15655     if (!cps->useSigint) return;
15656     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15657     switch (gameMode) {
15658       case MachinePlaysWhite:
15659       case MachinePlaysBlack:
15660       case TwoMachinesPlay:
15661       case IcsPlayingWhite:
15662       case IcsPlayingBlack:
15663       case AnalyzeMode:
15664       case AnalyzeFile:
15665         /* Skip if we know it isn't thinking */
15666         if (!cps->maybeThinking) return;
15667         if (appData.debugMode)
15668           fprintf(debugFP, "Interrupting %s\n", cps->which);
15669         InterruptChildProcess(cps->pr);
15670         cps->maybeThinking = FALSE;
15671         break;
15672       default:
15673         break;
15674     }
15675 #endif /*ATTENTION*/
15676 }
15677
15678 int
15679 CheckFlags ()
15680 {
15681     if (whiteTimeRemaining <= 0) {
15682         if (!whiteFlag) {
15683             whiteFlag = TRUE;
15684             if (appData.icsActive) {
15685                 if (appData.autoCallFlag &&
15686                     gameMode == IcsPlayingBlack && !blackFlag) {
15687                   SendToICS(ics_prefix);
15688                   SendToICS("flag\n");
15689                 }
15690             } else {
15691                 if (blackFlag) {
15692                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15693                 } else {
15694                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15695                     if (appData.autoCallFlag) {
15696                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15697                         return TRUE;
15698                     }
15699                 }
15700             }
15701         }
15702     }
15703     if (blackTimeRemaining <= 0) {
15704         if (!blackFlag) {
15705             blackFlag = TRUE;
15706             if (appData.icsActive) {
15707                 if (appData.autoCallFlag &&
15708                     gameMode == IcsPlayingWhite && !whiteFlag) {
15709                   SendToICS(ics_prefix);
15710                   SendToICS("flag\n");
15711                 }
15712             } else {
15713                 if (whiteFlag) {
15714                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15715                 } else {
15716                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15717                     if (appData.autoCallFlag) {
15718                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15719                         return TRUE;
15720                     }
15721                 }
15722             }
15723         }
15724     }
15725     return FALSE;
15726 }
15727
15728 void
15729 CheckTimeControl ()
15730 {
15731     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15732         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15733
15734     /*
15735      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15736      */
15737     if ( !WhiteOnMove(forwardMostMove) ) {
15738         /* White made time control */
15739         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15740         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15741         /* [HGM] time odds: correct new time quota for time odds! */
15742                                             / WhitePlayer()->timeOdds;
15743         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15744     } else {
15745         lastBlack -= blackTimeRemaining;
15746         /* Black made time control */
15747         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15748                                             / WhitePlayer()->other->timeOdds;
15749         lastWhite = whiteTimeRemaining;
15750     }
15751 }
15752
15753 void
15754 DisplayBothClocks ()
15755 {
15756     int wom = gameMode == EditPosition ?
15757       !blackPlaysFirst : WhiteOnMove(currentMove);
15758     DisplayWhiteClock(whiteTimeRemaining, wom);
15759     DisplayBlackClock(blackTimeRemaining, !wom);
15760 }
15761
15762
15763 /* Timekeeping seems to be a portability nightmare.  I think everyone
15764    has ftime(), but I'm really not sure, so I'm including some ifdefs
15765    to use other calls if you don't.  Clocks will be less accurate if
15766    you have neither ftime nor gettimeofday.
15767 */
15768
15769 /* VS 2008 requires the #include outside of the function */
15770 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15771 #include <sys/timeb.h>
15772 #endif
15773
15774 /* Get the current time as a TimeMark */
15775 void
15776 GetTimeMark (TimeMark *tm)
15777 {
15778 #if HAVE_GETTIMEOFDAY
15779
15780     struct timeval timeVal;
15781     struct timezone timeZone;
15782
15783     gettimeofday(&timeVal, &timeZone);
15784     tm->sec = (long) timeVal.tv_sec;
15785     tm->ms = (int) (timeVal.tv_usec / 1000L);
15786
15787 #else /*!HAVE_GETTIMEOFDAY*/
15788 #if HAVE_FTIME
15789
15790 // include <sys/timeb.h> / moved to just above start of function
15791     struct timeb timeB;
15792
15793     ftime(&timeB);
15794     tm->sec = (long) timeB.time;
15795     tm->ms = (int) timeB.millitm;
15796
15797 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15798     tm->sec = (long) time(NULL);
15799     tm->ms = 0;
15800 #endif
15801 #endif
15802 }
15803
15804 /* Return the difference in milliseconds between two
15805    time marks.  We assume the difference will fit in a long!
15806 */
15807 long
15808 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
15809 {
15810     return 1000L*(tm2->sec - tm1->sec) +
15811            (long) (tm2->ms - tm1->ms);
15812 }
15813
15814
15815 /*
15816  * Code to manage the game clocks.
15817  *
15818  * In tournament play, black starts the clock and then white makes a move.
15819  * We give the human user a slight advantage if he is playing white---the
15820  * clocks don't run until he makes his first move, so it takes zero time.
15821  * Also, we don't account for network lag, so we could get out of sync
15822  * with GNU Chess's clock -- but then, referees are always right.
15823  */
15824
15825 static TimeMark tickStartTM;
15826 static long intendedTickLength;
15827
15828 long
15829 NextTickLength (long timeRemaining)
15830 {
15831     long nominalTickLength, nextTickLength;
15832
15833     if (timeRemaining > 0L && timeRemaining <= 10000L)
15834       nominalTickLength = 100L;
15835     else
15836       nominalTickLength = 1000L;
15837     nextTickLength = timeRemaining % nominalTickLength;
15838     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15839
15840     return nextTickLength;
15841 }
15842
15843 /* Adjust clock one minute up or down */
15844 void
15845 AdjustClock (Boolean which, int dir)
15846 {
15847     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15848     if(which) blackTimeRemaining += 60000*dir;
15849     else      whiteTimeRemaining += 60000*dir;
15850     DisplayBothClocks();
15851     adjustedClock = TRUE;
15852 }
15853
15854 /* Stop clocks and reset to a fresh time control */
15855 void
15856 ResetClocks ()
15857 {
15858     (void) StopClockTimer();
15859     if (appData.icsActive) {
15860         whiteTimeRemaining = blackTimeRemaining = 0;
15861     } else if (searchTime) {
15862         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15863         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15864     } else { /* [HGM] correct new time quote for time odds */
15865         whiteTC = blackTC = fullTimeControlString;
15866         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15867         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15868     }
15869     if (whiteFlag || blackFlag) {
15870         DisplayTitle("");
15871         whiteFlag = blackFlag = FALSE;
15872     }
15873     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15874     DisplayBothClocks();
15875     adjustedClock = FALSE;
15876 }
15877
15878 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15879
15880 /* Decrement running clock by amount of time that has passed */
15881 void
15882 DecrementClocks ()
15883 {
15884     long timeRemaining;
15885     long lastTickLength, fudge;
15886     TimeMark now;
15887
15888     if (!appData.clockMode) return;
15889     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15890
15891     GetTimeMark(&now);
15892
15893     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15894
15895     /* Fudge if we woke up a little too soon */
15896     fudge = intendedTickLength - lastTickLength;
15897     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15898
15899     if (WhiteOnMove(forwardMostMove)) {
15900         if(whiteNPS >= 0) lastTickLength = 0;
15901         timeRemaining = whiteTimeRemaining -= lastTickLength;
15902         if(timeRemaining < 0 && !appData.icsActive) {
15903             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15904             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15905                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15906                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15907             }
15908         }
15909         DisplayWhiteClock(whiteTimeRemaining - fudge,
15910                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15911     } else {
15912         if(blackNPS >= 0) lastTickLength = 0;
15913         timeRemaining = blackTimeRemaining -= lastTickLength;
15914         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15915             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15916             if(suddenDeath) {
15917                 blackStartMove = forwardMostMove;
15918                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15919             }
15920         }
15921         DisplayBlackClock(blackTimeRemaining - fudge,
15922                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15923     }
15924     if (CheckFlags()) return;
15925
15926     tickStartTM = now;
15927     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15928     StartClockTimer(intendedTickLength);
15929
15930     /* if the time remaining has fallen below the alarm threshold, sound the
15931      * alarm. if the alarm has sounded and (due to a takeback or time control
15932      * with increment) the time remaining has increased to a level above the
15933      * threshold, reset the alarm so it can sound again.
15934      */
15935
15936     if (appData.icsActive && appData.icsAlarm) {
15937
15938         /* make sure we are dealing with the user's clock */
15939         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15940                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15941            )) return;
15942
15943         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15944             alarmSounded = FALSE;
15945         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15946             PlayAlarmSound();
15947             alarmSounded = TRUE;
15948         }
15949     }
15950 }
15951
15952
15953 /* A player has just moved, so stop the previously running
15954    clock and (if in clock mode) start the other one.
15955    We redisplay both clocks in case we're in ICS mode, because
15956    ICS gives us an update to both clocks after every move.
15957    Note that this routine is called *after* forwardMostMove
15958    is updated, so the last fractional tick must be subtracted
15959    from the color that is *not* on move now.
15960 */
15961 void
15962 SwitchClocks (int newMoveNr)
15963 {
15964     long lastTickLength;
15965     TimeMark now;
15966     int flagged = FALSE;
15967
15968     GetTimeMark(&now);
15969
15970     if (StopClockTimer() && appData.clockMode) {
15971         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15972         if (!WhiteOnMove(forwardMostMove)) {
15973             if(blackNPS >= 0) lastTickLength = 0;
15974             blackTimeRemaining -= lastTickLength;
15975            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15976 //         if(pvInfoList[forwardMostMove].time == -1)
15977                  pvInfoList[forwardMostMove].time =               // use GUI time
15978                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15979         } else {
15980            if(whiteNPS >= 0) lastTickLength = 0;
15981            whiteTimeRemaining -= lastTickLength;
15982            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15983 //         if(pvInfoList[forwardMostMove].time == -1)
15984                  pvInfoList[forwardMostMove].time =
15985                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15986         }
15987         flagged = CheckFlags();
15988     }
15989     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15990     CheckTimeControl();
15991
15992     if (flagged || !appData.clockMode) return;
15993
15994     switch (gameMode) {
15995       case MachinePlaysBlack:
15996       case MachinePlaysWhite:
15997       case BeginningOfGame:
15998         if (pausing) return;
15999         break;
16000
16001       case EditGame:
16002       case PlayFromGameFile:
16003       case IcsExamining:
16004         return;
16005
16006       default:
16007         break;
16008     }
16009
16010     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16011         if(WhiteOnMove(forwardMostMove))
16012              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16013         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16014     }
16015
16016     tickStartTM = now;
16017     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16018       whiteTimeRemaining : blackTimeRemaining);
16019     StartClockTimer(intendedTickLength);
16020 }
16021
16022
16023 /* Stop both clocks */
16024 void
16025 StopClocks ()
16026 {
16027     long lastTickLength;
16028     TimeMark now;
16029
16030     if (!StopClockTimer()) return;
16031     if (!appData.clockMode) return;
16032
16033     GetTimeMark(&now);
16034
16035     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16036     if (WhiteOnMove(forwardMostMove)) {
16037         if(whiteNPS >= 0) lastTickLength = 0;
16038         whiteTimeRemaining -= lastTickLength;
16039         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16040     } else {
16041         if(blackNPS >= 0) lastTickLength = 0;
16042         blackTimeRemaining -= lastTickLength;
16043         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16044     }
16045     CheckFlags();
16046 }
16047
16048 /* Start clock of player on move.  Time may have been reset, so
16049    if clock is already running, stop and restart it. */
16050 void
16051 StartClocks ()
16052 {
16053     (void) StopClockTimer(); /* in case it was running already */
16054     DisplayBothClocks();
16055     if (CheckFlags()) return;
16056
16057     if (!appData.clockMode) return;
16058     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16059
16060     GetTimeMark(&tickStartTM);
16061     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16062       whiteTimeRemaining : blackTimeRemaining);
16063
16064    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16065     whiteNPS = blackNPS = -1;
16066     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16067        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16068         whiteNPS = first.nps;
16069     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16070        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16071         blackNPS = first.nps;
16072     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16073         whiteNPS = second.nps;
16074     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16075         blackNPS = second.nps;
16076     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16077
16078     StartClockTimer(intendedTickLength);
16079 }
16080
16081 char *
16082 TimeString (long ms)
16083 {
16084     long second, minute, hour, day;
16085     char *sign = "";
16086     static char buf[32];
16087
16088     if (ms > 0 && ms <= 9900) {
16089       /* convert milliseconds to tenths, rounding up */
16090       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16091
16092       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16093       return buf;
16094     }
16095
16096     /* convert milliseconds to seconds, rounding up */
16097     /* use floating point to avoid strangeness of integer division
16098        with negative dividends on many machines */
16099     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16100
16101     if (second < 0) {
16102         sign = "-";
16103         second = -second;
16104     }
16105
16106     day = second / (60 * 60 * 24);
16107     second = second % (60 * 60 * 24);
16108     hour = second / (60 * 60);
16109     second = second % (60 * 60);
16110     minute = second / 60;
16111     second = second % 60;
16112
16113     if (day > 0)
16114       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16115               sign, day, hour, minute, second);
16116     else if (hour > 0)
16117       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16118     else
16119       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16120
16121     return buf;
16122 }
16123
16124
16125 /*
16126  * This is necessary because some C libraries aren't ANSI C compliant yet.
16127  */
16128 char *
16129 StrStr (char *string, char *match)
16130 {
16131     int i, length;
16132
16133     length = strlen(match);
16134
16135     for (i = strlen(string) - length; i >= 0; i--, string++)
16136       if (!strncmp(match, string, length))
16137         return string;
16138
16139     return NULL;
16140 }
16141
16142 char *
16143 StrCaseStr (char *string, char *match)
16144 {
16145     int i, j, length;
16146
16147     length = strlen(match);
16148
16149     for (i = strlen(string) - length; i >= 0; i--, string++) {
16150         for (j = 0; j < length; j++) {
16151             if (ToLower(match[j]) != ToLower(string[j]))
16152               break;
16153         }
16154         if (j == length) return string;
16155     }
16156
16157     return NULL;
16158 }
16159
16160 #ifndef _amigados
16161 int
16162 StrCaseCmp (char *s1, char *s2)
16163 {
16164     char c1, c2;
16165
16166     for (;;) {
16167         c1 = ToLower(*s1++);
16168         c2 = ToLower(*s2++);
16169         if (c1 > c2) return 1;
16170         if (c1 < c2) return -1;
16171         if (c1 == NULLCHAR) return 0;
16172     }
16173 }
16174
16175
16176 int
16177 ToLower (int c)
16178 {
16179     return isupper(c) ? tolower(c) : c;
16180 }
16181
16182
16183 int
16184 ToUpper (int c)
16185 {
16186     return islower(c) ? toupper(c) : c;
16187 }
16188 #endif /* !_amigados    */
16189
16190 char *
16191 StrSave (char *s)
16192 {
16193   char *ret;
16194
16195   if ((ret = (char *) malloc(strlen(s) + 1)))
16196     {
16197       safeStrCpy(ret, s, strlen(s)+1);
16198     }
16199   return ret;
16200 }
16201
16202 char *
16203 StrSavePtr (char *s, char **savePtr)
16204 {
16205     if (*savePtr) {
16206         free(*savePtr);
16207     }
16208     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16209       safeStrCpy(*savePtr, s, strlen(s)+1);
16210     }
16211     return(*savePtr);
16212 }
16213
16214 char *
16215 PGNDate ()
16216 {
16217     time_t clock;
16218     struct tm *tm;
16219     char buf[MSG_SIZ];
16220
16221     clock = time((time_t *)NULL);
16222     tm = localtime(&clock);
16223     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16224             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16225     return StrSave(buf);
16226 }
16227
16228
16229 char *
16230 PositionToFEN (int move, char *overrideCastling)
16231 {
16232     int i, j, fromX, fromY, toX, toY;
16233     int whiteToPlay;
16234     char buf[MSG_SIZ];
16235     char *p, *q;
16236     int emptycount;
16237     ChessSquare piece;
16238
16239     whiteToPlay = (gameMode == EditPosition) ?
16240       !blackPlaysFirst : (move % 2 == 0);
16241     p = buf;
16242
16243     /* Piece placement data */
16244     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16245         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16246         emptycount = 0;
16247         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16248             if (boards[move][i][j] == EmptySquare) {
16249                 emptycount++;
16250             } else { ChessSquare piece = boards[move][i][j];
16251                 if (emptycount > 0) {
16252                     if(emptycount<10) /* [HGM] can be >= 10 */
16253                         *p++ = '0' + emptycount;
16254                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16255                     emptycount = 0;
16256                 }
16257                 if(PieceToChar(piece) == '+') {
16258                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16259                     *p++ = '+';
16260                     piece = (ChessSquare)(DEMOTED piece);
16261                 }
16262                 *p++ = PieceToChar(piece);
16263                 if(p[-1] == '~') {
16264                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16265                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16266                     *p++ = '~';
16267                 }
16268             }
16269         }
16270         if (emptycount > 0) {
16271             if(emptycount<10) /* [HGM] can be >= 10 */
16272                 *p++ = '0' + emptycount;
16273             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16274             emptycount = 0;
16275         }
16276         *p++ = '/';
16277     }
16278     *(p - 1) = ' ';
16279
16280     /* [HGM] print Crazyhouse or Shogi holdings */
16281     if( gameInfo.holdingsWidth ) {
16282         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16283         q = p;
16284         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16285             piece = boards[move][i][BOARD_WIDTH-1];
16286             if( piece != EmptySquare )
16287               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16288                   *p++ = PieceToChar(piece);
16289         }
16290         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16291             piece = boards[move][BOARD_HEIGHT-i-1][0];
16292             if( piece != EmptySquare )
16293               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16294                   *p++ = PieceToChar(piece);
16295         }
16296
16297         if( q == p ) *p++ = '-';
16298         *p++ = ']';
16299         *p++ = ' ';
16300     }
16301
16302     /* Active color */
16303     *p++ = whiteToPlay ? 'w' : 'b';
16304     *p++ = ' ';
16305
16306   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16307     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16308   } else {
16309   if(nrCastlingRights) {
16310      q = p;
16311      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16312        /* [HGM] write directly from rights */
16313            if(boards[move][CASTLING][2] != NoRights &&
16314               boards[move][CASTLING][0] != NoRights   )
16315                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16316            if(boards[move][CASTLING][2] != NoRights &&
16317               boards[move][CASTLING][1] != NoRights   )
16318                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16319            if(boards[move][CASTLING][5] != NoRights &&
16320               boards[move][CASTLING][3] != NoRights   )
16321                 *p++ = boards[move][CASTLING][3] + AAA;
16322            if(boards[move][CASTLING][5] != NoRights &&
16323               boards[move][CASTLING][4] != NoRights   )
16324                 *p++ = boards[move][CASTLING][4] + AAA;
16325      } else {
16326
16327         /* [HGM] write true castling rights */
16328         if( nrCastlingRights == 6 ) {
16329             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16330                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16331             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16332                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16333             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16334                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16335             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16336                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16337         }
16338      }
16339      if (q == p) *p++ = '-'; /* No castling rights */
16340      *p++ = ' ';
16341   }
16342
16343   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16344      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16345     /* En passant target square */
16346     if (move > backwardMostMove) {
16347         fromX = moveList[move - 1][0] - AAA;
16348         fromY = moveList[move - 1][1] - ONE;
16349         toX = moveList[move - 1][2] - AAA;
16350         toY = moveList[move - 1][3] - ONE;
16351         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16352             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16353             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16354             fromX == toX) {
16355             /* 2-square pawn move just happened */
16356             *p++ = toX + AAA;
16357             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16358         } else {
16359             *p++ = '-';
16360         }
16361     } else if(move == backwardMostMove) {
16362         // [HGM] perhaps we should always do it like this, and forget the above?
16363         if((signed char)boards[move][EP_STATUS] >= 0) {
16364             *p++ = boards[move][EP_STATUS] + AAA;
16365             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16366         } else {
16367             *p++ = '-';
16368         }
16369     } else {
16370         *p++ = '-';
16371     }
16372     *p++ = ' ';
16373   }
16374   }
16375
16376     /* [HGM] find reversible plies */
16377     {   int i = 0, j=move;
16378
16379         if (appData.debugMode) { int k;
16380             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16381             for(k=backwardMostMove; k<=forwardMostMove; k++)
16382                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16383
16384         }
16385
16386         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16387         if( j == backwardMostMove ) i += initialRulePlies;
16388         sprintf(p, "%d ", i);
16389         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16390     }
16391     /* Fullmove number */
16392     sprintf(p, "%d", (move / 2) + 1);
16393
16394     return StrSave(buf);
16395 }
16396
16397 Boolean
16398 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16399 {
16400     int i, j;
16401     char *p, c;
16402     int emptycount;
16403     ChessSquare piece;
16404
16405     p = fen;
16406
16407     /* [HGM] by default clear Crazyhouse holdings, if present */
16408     if(gameInfo.holdingsWidth) {
16409        for(i=0; i<BOARD_HEIGHT; i++) {
16410            board[i][0]             = EmptySquare; /* black holdings */
16411            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16412            board[i][1]             = (ChessSquare) 0; /* black counts */
16413            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16414        }
16415     }
16416
16417     /* Piece placement data */
16418     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16419         j = 0;
16420         for (;;) {
16421             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16422                 if (*p == '/') p++;
16423                 emptycount = gameInfo.boardWidth - j;
16424                 while (emptycount--)
16425                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16426                 break;
16427 #if(BOARD_FILES >= 10)
16428             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16429                 p++; emptycount=10;
16430                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16431                 while (emptycount--)
16432                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16433 #endif
16434             } else if (isdigit(*p)) {
16435                 emptycount = *p++ - '0';
16436                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16437                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16438                 while (emptycount--)
16439                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16440             } else if (*p == '+' || isalpha(*p)) {
16441                 if (j >= gameInfo.boardWidth) return FALSE;
16442                 if(*p=='+') {
16443                     piece = CharToPiece(*++p);
16444                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16445                     piece = (ChessSquare) (PROMOTED piece ); p++;
16446                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16447                 } else piece = CharToPiece(*p++);
16448
16449                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16450                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16451                     piece = (ChessSquare) (PROMOTED piece);
16452                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16453                     p++;
16454                 }
16455                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16456             } else {
16457                 return FALSE;
16458             }
16459         }
16460     }
16461     while (*p == '/' || *p == ' ') p++;
16462
16463     /* [HGM] look for Crazyhouse holdings here */
16464     while(*p==' ') p++;
16465     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16466         if(*p == '[') p++;
16467         if(*p == '-' ) p++; /* empty holdings */ else {
16468             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16469             /* if we would allow FEN reading to set board size, we would   */
16470             /* have to add holdings and shift the board read so far here   */
16471             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16472                 p++;
16473                 if((int) piece >= (int) BlackPawn ) {
16474                     i = (int)piece - (int)BlackPawn;
16475                     i = PieceToNumber((ChessSquare)i);
16476                     if( i >= gameInfo.holdingsSize ) return FALSE;
16477                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16478                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16479                 } else {
16480                     i = (int)piece - (int)WhitePawn;
16481                     i = PieceToNumber((ChessSquare)i);
16482                     if( i >= gameInfo.holdingsSize ) return FALSE;
16483                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16484                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16485                 }
16486             }
16487         }
16488         if(*p == ']') p++;
16489     }
16490
16491     while(*p == ' ') p++;
16492
16493     /* Active color */
16494     c = *p++;
16495     if(appData.colorNickNames) {
16496       if( c == appData.colorNickNames[0] ) c = 'w'; else
16497       if( c == appData.colorNickNames[1] ) c = 'b';
16498     }
16499     switch (c) {
16500       case 'w':
16501         *blackPlaysFirst = FALSE;
16502         break;
16503       case 'b':
16504         *blackPlaysFirst = TRUE;
16505         break;
16506       default:
16507         return FALSE;
16508     }
16509
16510     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16511     /* return the extra info in global variiables             */
16512
16513     /* set defaults in case FEN is incomplete */
16514     board[EP_STATUS] = EP_UNKNOWN;
16515     for(i=0; i<nrCastlingRights; i++ ) {
16516         board[CASTLING][i] =
16517             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16518     }   /* assume possible unless obviously impossible */
16519     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16520     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16521     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16522                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16523     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16524     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16525     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16526                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16527     FENrulePlies = 0;
16528
16529     while(*p==' ') p++;
16530     if(nrCastlingRights) {
16531       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16532           /* castling indicator present, so default becomes no castlings */
16533           for(i=0; i<nrCastlingRights; i++ ) {
16534                  board[CASTLING][i] = NoRights;
16535           }
16536       }
16537       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16538              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16539              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16540              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16541         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16542
16543         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16544             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16545             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16546         }
16547         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16548             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16549         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16550                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16551         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16552                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16553         switch(c) {
16554           case'K':
16555               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16556               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16557               board[CASTLING][2] = whiteKingFile;
16558               break;
16559           case'Q':
16560               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16561               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16562               board[CASTLING][2] = whiteKingFile;
16563               break;
16564           case'k':
16565               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16566               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16567               board[CASTLING][5] = blackKingFile;
16568               break;
16569           case'q':
16570               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16571               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16572               board[CASTLING][5] = blackKingFile;
16573           case '-':
16574               break;
16575           default: /* FRC castlings */
16576               if(c >= 'a') { /* black rights */
16577                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16578                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16579                   if(i == BOARD_RGHT) break;
16580                   board[CASTLING][5] = i;
16581                   c -= AAA;
16582                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16583                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16584                   if(c > i)
16585                       board[CASTLING][3] = c;
16586                   else
16587                       board[CASTLING][4] = c;
16588               } else { /* white rights */
16589                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16590                     if(board[0][i] == WhiteKing) break;
16591                   if(i == BOARD_RGHT) break;
16592                   board[CASTLING][2] = i;
16593                   c -= AAA - 'a' + 'A';
16594                   if(board[0][c] >= WhiteKing) break;
16595                   if(c > i)
16596                       board[CASTLING][0] = c;
16597                   else
16598                       board[CASTLING][1] = c;
16599               }
16600         }
16601       }
16602       for(i=0; i<nrCastlingRights; i++)
16603         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16604     if (appData.debugMode) {
16605         fprintf(debugFP, "FEN castling rights:");
16606         for(i=0; i<nrCastlingRights; i++)
16607         fprintf(debugFP, " %d", board[CASTLING][i]);
16608         fprintf(debugFP, "\n");
16609     }
16610
16611       while(*p==' ') p++;
16612     }
16613
16614     /* read e.p. field in games that know e.p. capture */
16615     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16616        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16617       if(*p=='-') {
16618         p++; board[EP_STATUS] = EP_NONE;
16619       } else {
16620          char c = *p++ - AAA;
16621
16622          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16623          if(*p >= '0' && *p <='9') p++;
16624          board[EP_STATUS] = c;
16625       }
16626     }
16627
16628
16629     if(sscanf(p, "%d", &i) == 1) {
16630         FENrulePlies = i; /* 50-move ply counter */
16631         /* (The move number is still ignored)    */
16632     }
16633
16634     return TRUE;
16635 }
16636
16637 void
16638 EditPositionPasteFEN (char *fen)
16639 {
16640   if (fen != NULL) {
16641     Board initial_position;
16642
16643     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16644       DisplayError(_("Bad FEN position in clipboard"), 0);
16645       return ;
16646     } else {
16647       int savedBlackPlaysFirst = blackPlaysFirst;
16648       EditPositionEvent();
16649       blackPlaysFirst = savedBlackPlaysFirst;
16650       CopyBoard(boards[0], initial_position);
16651       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16652       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16653       DisplayBothClocks();
16654       DrawPosition(FALSE, boards[currentMove]);
16655     }
16656   }
16657 }
16658
16659 static char cseq[12] = "\\   ";
16660
16661 Boolean
16662 set_cont_sequence (char *new_seq)
16663 {
16664     int len;
16665     Boolean ret;
16666
16667     // handle bad attempts to set the sequence
16668         if (!new_seq)
16669                 return 0; // acceptable error - no debug
16670
16671     len = strlen(new_seq);
16672     ret = (len > 0) && (len < sizeof(cseq));
16673     if (ret)
16674       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16675     else if (appData.debugMode)
16676       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16677     return ret;
16678 }
16679
16680 /*
16681     reformat a source message so words don't cross the width boundary.  internal
16682     newlines are not removed.  returns the wrapped size (no null character unless
16683     included in source message).  If dest is NULL, only calculate the size required
16684     for the dest buffer.  lp argument indicats line position upon entry, and it's
16685     passed back upon exit.
16686 */
16687 int
16688 wrap (char *dest, char *src, int count, int width, int *lp)
16689 {
16690     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16691
16692     cseq_len = strlen(cseq);
16693     old_line = line = *lp;
16694     ansi = len = clen = 0;
16695
16696     for (i=0; i < count; i++)
16697     {
16698         if (src[i] == '\033')
16699             ansi = 1;
16700
16701         // if we hit the width, back up
16702         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16703         {
16704             // store i & len in case the word is too long
16705             old_i = i, old_len = len;
16706
16707             // find the end of the last word
16708             while (i && src[i] != ' ' && src[i] != '\n')
16709             {
16710                 i--;
16711                 len--;
16712             }
16713
16714             // word too long?  restore i & len before splitting it
16715             if ((old_i-i+clen) >= width)
16716             {
16717                 i = old_i;
16718                 len = old_len;
16719             }
16720
16721             // extra space?
16722             if (i && src[i-1] == ' ')
16723                 len--;
16724
16725             if (src[i] != ' ' && src[i] != '\n')
16726             {
16727                 i--;
16728                 if (len)
16729                     len--;
16730             }
16731
16732             // now append the newline and continuation sequence
16733             if (dest)
16734                 dest[len] = '\n';
16735             len++;
16736             if (dest)
16737                 strncpy(dest+len, cseq, cseq_len);
16738             len += cseq_len;
16739             line = cseq_len;
16740             clen = cseq_len;
16741             continue;
16742         }
16743
16744         if (dest)
16745             dest[len] = src[i];
16746         len++;
16747         if (!ansi)
16748             line++;
16749         if (src[i] == '\n')
16750             line = 0;
16751         if (src[i] == 'm')
16752             ansi = 0;
16753     }
16754     if (dest && appData.debugMode)
16755     {
16756         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16757             count, width, line, len, *lp);
16758         show_bytes(debugFP, src, count);
16759         fprintf(debugFP, "\ndest: ");
16760         show_bytes(debugFP, dest, len);
16761         fprintf(debugFP, "\n");
16762     }
16763     *lp = dest ? line : old_line;
16764
16765     return len;
16766 }
16767
16768 // [HGM] vari: routines for shelving variations
16769 Boolean modeRestore = FALSE;
16770
16771 void
16772 PushInner (int firstMove, int lastMove)
16773 {
16774         int i, j, nrMoves = lastMove - firstMove;
16775
16776         // push current tail of game on stack
16777         savedResult[storedGames] = gameInfo.result;
16778         savedDetails[storedGames] = gameInfo.resultDetails;
16779         gameInfo.resultDetails = NULL;
16780         savedFirst[storedGames] = firstMove;
16781         savedLast [storedGames] = lastMove;
16782         savedFramePtr[storedGames] = framePtr;
16783         framePtr -= nrMoves; // reserve space for the boards
16784         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16785             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16786             for(j=0; j<MOVE_LEN; j++)
16787                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16788             for(j=0; j<2*MOVE_LEN; j++)
16789                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16790             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16791             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16792             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16793             pvInfoList[firstMove+i-1].depth = 0;
16794             commentList[framePtr+i] = commentList[firstMove+i];
16795             commentList[firstMove+i] = NULL;
16796         }
16797
16798         storedGames++;
16799         forwardMostMove = firstMove; // truncate game so we can start variation
16800 }
16801
16802 void
16803 PushTail (int firstMove, int lastMove)
16804 {
16805         if(appData.icsActive) { // only in local mode
16806                 forwardMostMove = currentMove; // mimic old ICS behavior
16807                 return;
16808         }
16809         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16810
16811         PushInner(firstMove, lastMove);
16812         if(storedGames == 1) GreyRevert(FALSE);
16813         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16814 }
16815
16816 void
16817 PopInner (Boolean annotate)
16818 {
16819         int i, j, nrMoves;
16820         char buf[8000], moveBuf[20];
16821
16822         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16823         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16824         nrMoves = savedLast[storedGames] - currentMove;
16825         if(annotate) {
16826                 int cnt = 10;
16827                 if(!WhiteOnMove(currentMove))
16828                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16829                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16830                 for(i=currentMove; i<forwardMostMove; i++) {
16831                         if(WhiteOnMove(i))
16832                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16833                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16834                         strcat(buf, moveBuf);
16835                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16836                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16837                 }
16838                 strcat(buf, ")");
16839         }
16840         for(i=1; i<=nrMoves; i++) { // copy last variation back
16841             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16842             for(j=0; j<MOVE_LEN; j++)
16843                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16844             for(j=0; j<2*MOVE_LEN; j++)
16845                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16846             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16847             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16848             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16849             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16850             commentList[currentMove+i] = commentList[framePtr+i];
16851             commentList[framePtr+i] = NULL;
16852         }
16853         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16854         framePtr = savedFramePtr[storedGames];
16855         gameInfo.result = savedResult[storedGames];
16856         if(gameInfo.resultDetails != NULL) {
16857             free(gameInfo.resultDetails);
16858       }
16859         gameInfo.resultDetails = savedDetails[storedGames];
16860         forwardMostMove = currentMove + nrMoves;
16861 }
16862
16863 Boolean
16864 PopTail (Boolean annotate)
16865 {
16866         if(appData.icsActive) return FALSE; // only in local mode
16867         if(!storedGames) return FALSE; // sanity
16868         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16869
16870         PopInner(annotate);
16871         if(currentMove < forwardMostMove) ForwardEvent(); else
16872         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16873
16874         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16875         return TRUE;
16876 }
16877
16878 void
16879 CleanupTail ()
16880 {       // remove all shelved variations
16881         int i;
16882         for(i=0; i<storedGames; i++) {
16883             if(savedDetails[i])
16884                 free(savedDetails[i]);
16885             savedDetails[i] = NULL;
16886         }
16887         for(i=framePtr; i<MAX_MOVES; i++) {
16888                 if(commentList[i]) free(commentList[i]);
16889                 commentList[i] = NULL;
16890         }
16891         framePtr = MAX_MOVES-1;
16892         storedGames = 0;
16893 }
16894
16895 void
16896 LoadVariation (int index, char *text)
16897 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16898         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16899         int level = 0, move;
16900
16901         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16902         // first find outermost bracketing variation
16903         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16904             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16905                 if(*p == '{') wait = '}'; else
16906                 if(*p == '[') wait = ']'; else
16907                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16908                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16909             }
16910             if(*p == wait) wait = NULLCHAR; // closing ]} found
16911             p++;
16912         }
16913         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16914         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16915         end[1] = NULLCHAR; // clip off comment beyond variation
16916         ToNrEvent(currentMove-1);
16917         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16918         // kludge: use ParsePV() to append variation to game
16919         move = currentMove;
16920         ParsePV(start, TRUE, TRUE);
16921         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16922         ClearPremoveHighlights();
16923         CommentPopDown();
16924         ToNrEvent(currentMove+1);
16925 }
16926