Suppress empty lines when observing on VICS
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "gettext.h"
133
134 #ifdef ENABLE_NLS
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
138 #else
139 # ifdef WIN32
140 #   define _(s) T_(s)
141 #   define N_(s) s
142 # else
143 #   define _(s) (s)
144 #   define N_(s) s
145 #   define T_(s) s
146 # endif
147 #endif
148
149
150 int establish P((void));
151 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
152                          char *buf, int count, int error));
153 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
154                       char *buf, int count, int error));
155 void ics_printf P((char *format, ...));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225
226 #ifdef WIN32
227        extern void ConsoleCreate();
228 #endif
229
230 ChessProgramState *WhitePlayer();
231 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
232 int VerifyDisplayMode P(());
233
234 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
235 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
236 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
237 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
238 void ics_update_width P((int new_width));
239 extern char installDir[MSG_SIZ];
240 VariantClass startVariant; /* [HGM] nicks: initial variant */
241 Boolean abortMatch;
242
243 extern int tinyLayout, smallLayout;
244 ChessProgramStats programStats;
245 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
246 int endPV = -1;
247 static int exiting = 0; /* [HGM] moved to top */
248 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
249 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
250 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
251 int partnerHighlight[2];
252 Boolean partnerBoardValid = 0;
253 char partnerStatus[MSG_SIZ];
254 Boolean partnerUp;
255 Boolean originalFlip;
256 Boolean twoBoards = 0;
257 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
258 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
259 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
260 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
261 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
262 int opponentKibitzes;
263 int lastSavedGame; /* [HGM] save: ID of game */
264 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
265 extern int chatCount;
266 int chattingPartner;
267 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
268 char lastMsg[MSG_SIZ];
269 ChessSquare pieceSweep = EmptySquare;
270 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
271 int promoDefaultAltered;
272
273 /* States for ics_getting_history */
274 #define H_FALSE 0
275 #define H_REQUESTED 1
276 #define H_GOT_REQ_HEADER 2
277 #define H_GOT_UNREQ_HEADER 3
278 #define H_GETTING_MOVES 4
279 #define H_GOT_UNWANTED_HEADER 5
280
281 /* whosays values for GameEnds */
282 #define GE_ICS 0
283 #define GE_ENGINE 1
284 #define GE_PLAYER 2
285 #define GE_FILE 3
286 #define GE_XBOARD 4
287 #define GE_ENGINE1 5
288 #define GE_ENGINE2 6
289
290 /* Maximum number of games in a cmail message */
291 #define CMAIL_MAX_GAMES 20
292
293 /* Different types of move when calling RegisterMove */
294 #define CMAIL_MOVE   0
295 #define CMAIL_RESIGN 1
296 #define CMAIL_DRAW   2
297 #define CMAIL_ACCEPT 3
298
299 /* Different types of result to remember for each game */
300 #define CMAIL_NOT_RESULT 0
301 #define CMAIL_OLD_RESULT 1
302 #define CMAIL_NEW_RESULT 2
303
304 /* Telnet protocol constants */
305 #define TN_WILL 0373
306 #define TN_WONT 0374
307 #define TN_DO   0375
308 #define TN_DONT 0376
309 #define TN_IAC  0377
310 #define TN_ECHO 0001
311 #define TN_SGA  0003
312 #define TN_PORT 23
313
314 char*
315 safeStrCpy (char *dst, const char *src, size_t count)
316 { // [HGM] made safe
317   int i;
318   assert( dst != NULL );
319   assert( src != NULL );
320   assert( count > 0 );
321
322   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
323   if(  i == count && dst[count-1] != NULLCHAR)
324     {
325       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
326       if(appData.debugMode)
327       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
328     }
329
330   return dst;
331 }
332
333 /* Some compiler can't cast u64 to double
334  * This function do the job for us:
335
336  * We use the highest bit for cast, this only
337  * works if the highest bit is not
338  * in use (This should not happen)
339  *
340  * We used this for all compiler
341  */
342 double
343 u64ToDouble (u64 value)
344 {
345   double r;
346   u64 tmp = value & u64Const(0x7fffffffffffffff);
347   r = (double)(s64)tmp;
348   if (value & u64Const(0x8000000000000000))
349        r +=  9.2233720368547758080e18; /* 2^63 */
350  return r;
351 }
352
353 /* Fake up flags for now, as we aren't keeping track of castling
354    availability yet. [HGM] Change of logic: the flag now only
355    indicates the type of castlings allowed by the rule of the game.
356    The actual rights themselves are maintained in the array
357    castlingRights, as part of the game history, and are not probed
358    by this function.
359  */
360 int
361 PosFlags (index)
362 {
363   int flags = F_ALL_CASTLE_OK;
364   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
365   switch (gameInfo.variant) {
366   case VariantSuicide:
367     flags &= ~F_ALL_CASTLE_OK;
368   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
369     flags |= F_IGNORE_CHECK;
370   case VariantLosers:
371     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
372     break;
373   case VariantAtomic:
374     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
375     break;
376   case VariantKriegspiel:
377     flags |= F_KRIEGSPIEL_CAPTURE;
378     break;
379   case VariantCapaRandom:
380   case VariantFischeRandom:
381     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
382   case VariantNoCastle:
383   case VariantShatranj:
384   case VariantCourier:
385   case VariantMakruk:
386   case VariantGrand:
387     flags &= ~F_ALL_CASTLE_OK;
388     break;
389   default:
390     break;
391   }
392   return flags;
393 }
394
395 FILE *gameFileFP, *debugFP;
396
397 /*
398     [AS] Note: sometimes, the sscanf() function is used to parse the input
399     into a fixed-size buffer. Because of this, we must be prepared to
400     receive strings as long as the size of the input buffer, which is currently
401     set to 4K for Windows and 8K for the rest.
402     So, we must either allocate sufficiently large buffers here, or
403     reduce the size of the input buffer in the input reading part.
404 */
405
406 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
407 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
408 char thinkOutput1[MSG_SIZ*10];
409
410 ChessProgramState first, second, pairing;
411
412 /* premove variables */
413 int premoveToX = 0;
414 int premoveToY = 0;
415 int premoveFromX = 0;
416 int premoveFromY = 0;
417 int premovePromoChar = 0;
418 int gotPremove = 0;
419 Boolean alarmSounded;
420 /* end premove variables */
421
422 char *ics_prefix = "$";
423 int ics_type = ICS_GENERIC;
424
425 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
426 int pauseExamForwardMostMove = 0;
427 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
428 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
429 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
430 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
431 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
432 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
433 int whiteFlag = FALSE, blackFlag = FALSE;
434 int userOfferedDraw = FALSE;
435 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
436 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
437 int cmailMoveType[CMAIL_MAX_GAMES];
438 long ics_clock_paused = 0;
439 ProcRef icsPR = NoProc, cmailPR = NoProc;
440 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
441 GameMode gameMode = BeginningOfGame;
442 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
443 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
444 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
445 int hiddenThinkOutputState = 0; /* [AS] */
446 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
447 int adjudicateLossPlies = 6;
448 char white_holding[64], black_holding[64];
449 TimeMark lastNodeCountTime;
450 long lastNodeCount=0;
451 int shiftKey; // [HGM] set by mouse handler
452
453 int have_sent_ICS_logon = 0;
454 int movesPerSession;
455 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
456 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
457 Boolean adjustedClock;
458 long timeControl_2; /* [AS] Allow separate time controls */
459 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
460 long timeRemaining[2][MAX_MOVES];
461 int matchGame = 0, nextGame = 0, roundNr = 0;
462 Boolean waitingForGame = FALSE;
463 TimeMark programStartTime, pauseStart;
464 char ics_handle[MSG_SIZ];
465 int have_set_title = 0;
466
467 /* animateTraining preserves the state of appData.animate
468  * when Training mode is activated. This allows the
469  * response to be animated when appData.animate == TRUE and
470  * appData.animateDragging == TRUE.
471  */
472 Boolean animateTraining;
473
474 GameInfo gameInfo;
475
476 AppData appData;
477
478 Board boards[MAX_MOVES];
479 /* [HGM] Following 7 needed for accurate legality tests: */
480 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
481 signed char  initialRights[BOARD_FILES];
482 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
483 int   initialRulePlies, FENrulePlies;
484 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
485 int loadFlag = 0;
486 Boolean shuffleOpenings;
487 int mute; // mute all sounds
488
489 // [HGM] vari: next 12 to save and restore variations
490 #define MAX_VARIATIONS 10
491 int framePtr = MAX_MOVES-1; // points to free stack entry
492 int storedGames = 0;
493 int savedFirst[MAX_VARIATIONS];
494 int savedLast[MAX_VARIATIONS];
495 int savedFramePtr[MAX_VARIATIONS];
496 char *savedDetails[MAX_VARIATIONS];
497 ChessMove savedResult[MAX_VARIATIONS];
498
499 void PushTail P((int firstMove, int lastMove));
500 Boolean PopTail P((Boolean annotate));
501 void PushInner P((int firstMove, int lastMove));
502 void PopInner P((Boolean annotate));
503 void CleanupTail P((void));
504
505 ChessSquare  FIDEArray[2][BOARD_FILES] = {
506     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
507         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
508     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
509         BlackKing, BlackBishop, BlackKnight, BlackRook }
510 };
511
512 ChessSquare twoKingsArray[2][BOARD_FILES] = {
513     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
514         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
515     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
516         BlackKing, BlackKing, BlackKnight, BlackRook }
517 };
518
519 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
520     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
521         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
522     { BlackRook, BlackMan, BlackBishop, BlackQueen,
523         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
524 };
525
526 ChessSquare SpartanArray[2][BOARD_FILES] = {
527     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
528         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
529     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
530         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
531 };
532
533 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
534     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
535         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
536     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
537         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
538 };
539
540 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
541     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
542         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
544         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
545 };
546
547 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
548     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
549         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
550     { BlackRook, BlackKnight, BlackMan, BlackFerz,
551         BlackKing, BlackMan, BlackKnight, BlackRook }
552 };
553
554
555 #if (BOARD_FILES>=10)
556 ChessSquare ShogiArray[2][BOARD_FILES] = {
557     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
558         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
559     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
560         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
561 };
562
563 ChessSquare XiangqiArray[2][BOARD_FILES] = {
564     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
565         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
566     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
567         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
568 };
569
570 ChessSquare CapablancaArray[2][BOARD_FILES] = {
571     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
572         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
573     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
574         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
575 };
576
577 ChessSquare GreatArray[2][BOARD_FILES] = {
578     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
579         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
580     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
581         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
582 };
583
584 ChessSquare JanusArray[2][BOARD_FILES] = {
585     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
586         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
587     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
588         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
589 };
590
591 ChessSquare GrandArray[2][BOARD_FILES] = {
592     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
593         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
594     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
595         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
596 };
597
598 #ifdef GOTHIC
599 ChessSquare GothicArray[2][BOARD_FILES] = {
600     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
601         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
602     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
603         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
604 };
605 #else // !GOTHIC
606 #define GothicArray CapablancaArray
607 #endif // !GOTHIC
608
609 #ifdef FALCON
610 ChessSquare FalconArray[2][BOARD_FILES] = {
611     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
612         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
613     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
614         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
615 };
616 #else // !FALCON
617 #define FalconArray CapablancaArray
618 #endif // !FALCON
619
620 #else // !(BOARD_FILES>=10)
621 #define XiangqiPosition FIDEArray
622 #define CapablancaArray FIDEArray
623 #define GothicArray FIDEArray
624 #define GreatArray FIDEArray
625 #endif // !(BOARD_FILES>=10)
626
627 #if (BOARD_FILES>=12)
628 ChessSquare CourierArray[2][BOARD_FILES] = {
629     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
630         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
631     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
632         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
633 };
634 #else // !(BOARD_FILES>=12)
635 #define CourierArray CapablancaArray
636 #endif // !(BOARD_FILES>=12)
637
638
639 Board initialPosition;
640
641
642 /* Convert str to a rating. Checks for special cases of "----",
643
644    "++++", etc. Also strips ()'s */
645 int
646 string_to_rating (char *str)
647 {
648   while(*str && !isdigit(*str)) ++str;
649   if (!*str)
650     return 0;   /* One of the special "no rating" cases */
651   else
652     return atoi(str);
653 }
654
655 void
656 ClearProgramStats ()
657 {
658     /* Init programStats */
659     programStats.movelist[0] = 0;
660     programStats.depth = 0;
661     programStats.nr_moves = 0;
662     programStats.moves_left = 0;
663     programStats.nodes = 0;
664     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
665     programStats.score = 0;
666     programStats.got_only_move = 0;
667     programStats.got_fail = 0;
668     programStats.line_is_book = 0;
669 }
670
671 void
672 CommonEngineInit ()
673 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
674     if (appData.firstPlaysBlack) {
675         first.twoMachinesColor = "black\n";
676         second.twoMachinesColor = "white\n";
677     } else {
678         first.twoMachinesColor = "white\n";
679         second.twoMachinesColor = "black\n";
680     }
681
682     first.other = &second;
683     second.other = &first;
684
685     { float norm = 1;
686         if(appData.timeOddsMode) {
687             norm = appData.timeOdds[0];
688             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
689         }
690         first.timeOdds  = appData.timeOdds[0]/norm;
691         second.timeOdds = appData.timeOdds[1]/norm;
692     }
693
694     if(programVersion) free(programVersion);
695     if (appData.noChessProgram) {
696         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
697         sprintf(programVersion, "%s", PACKAGE_STRING);
698     } else {
699       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
700       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
701       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
702     }
703 }
704
705 void
706 UnloadEngine (ChessProgramState *cps)
707 {
708         /* Kill off first chess program */
709         if (cps->isr != NULL)
710           RemoveInputSource(cps->isr);
711         cps->isr = NULL;
712
713         if (cps->pr != NoProc) {
714             ExitAnalyzeMode();
715             DoSleep( appData.delayBeforeQuit );
716             SendToProgram("quit\n", cps);
717             DoSleep( appData.delayAfterQuit );
718             DestroyChildProcess(cps->pr, cps->useSigterm);
719         }
720         cps->pr = NoProc;
721         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
722 }
723
724 void
725 ClearOptions (ChessProgramState *cps)
726 {
727     int i;
728     cps->nrOptions = cps->comboCnt = 0;
729     for(i=0; i<MAX_OPTIONS; i++) {
730         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
731         cps->option[i].textValue = 0;
732     }
733 }
734
735 char *engineNames[] = {
736 "first",
737 "second"
738 };
739
740 void
741 InitEngine (ChessProgramState *cps, int n)
742 {   // [HGM] all engine initialiation put in a function that does one engine
743
744     ClearOptions(cps);
745
746     cps->which = engineNames[n];
747     cps->maybeThinking = FALSE;
748     cps->pr = NoProc;
749     cps->isr = NULL;
750     cps->sendTime = 2;
751     cps->sendDrawOffers = 1;
752
753     cps->program = appData.chessProgram[n];
754     cps->host = appData.host[n];
755     cps->dir = appData.directory[n];
756     cps->initString = appData.engInitString[n];
757     cps->computerString = appData.computerString[n];
758     cps->useSigint  = TRUE;
759     cps->useSigterm = TRUE;
760     cps->reuse = appData.reuse[n];
761     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
762     cps->useSetboard = FALSE;
763     cps->useSAN = FALSE;
764     cps->usePing = FALSE;
765     cps->lastPing = 0;
766     cps->lastPong = 0;
767     cps->usePlayother = FALSE;
768     cps->useColors = TRUE;
769     cps->useUsermove = FALSE;
770     cps->sendICS = FALSE;
771     cps->sendName = appData.icsActive;
772     cps->sdKludge = FALSE;
773     cps->stKludge = FALSE;
774     TidyProgramName(cps->program, cps->host, cps->tidy);
775     cps->matchWins = 0;
776     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
777     cps->analysisSupport = 2; /* detect */
778     cps->analyzing = FALSE;
779     cps->initDone = FALSE;
780
781     /* New features added by Tord: */
782     cps->useFEN960 = FALSE;
783     cps->useOOCastle = TRUE;
784     /* End of new features added by Tord. */
785     cps->fenOverride  = appData.fenOverride[n];
786
787     /* [HGM] time odds: set factor for each machine */
788     cps->timeOdds  = appData.timeOdds[n];
789
790     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
791     cps->accumulateTC = appData.accumulateTC[n];
792     cps->maxNrOfSessions = 1;
793
794     /* [HGM] debug */
795     cps->debug = FALSE;
796
797     cps->supportsNPS = UNKNOWN;
798     cps->memSize = FALSE;
799     cps->maxCores = FALSE;
800     cps->egtFormats[0] = NULLCHAR;
801
802     /* [HGM] options */
803     cps->optionSettings  = appData.engOptions[n];
804
805     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
806     cps->isUCI = appData.isUCI[n]; /* [AS] */
807     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
808
809     if (appData.protocolVersion[n] > PROTOVER
810         || appData.protocolVersion[n] < 1)
811       {
812         char buf[MSG_SIZ];
813         int len;
814
815         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
816                        appData.protocolVersion[n]);
817         if( (len >= MSG_SIZ) && appData.debugMode )
818           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
819
820         DisplayFatalError(buf, 0, 2);
821       }
822     else
823       {
824         cps->protocolVersion = appData.protocolVersion[n];
825       }
826
827     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
828     ParseFeatures(appData.featureDefaults, cps);
829 }
830
831 ChessProgramState *savCps;
832
833 void
834 LoadEngine ()
835 {
836     int i;
837     if(WaitForEngine(savCps, LoadEngine)) return;
838     CommonEngineInit(); // recalculate time odds
839     if(gameInfo.variant != StringToVariant(appData.variant)) {
840         // we changed variant when loading the engine; this forces us to reset
841         Reset(TRUE, savCps != &first);
842         EditGameEvent(); // for consistency with other path, as Reset changes mode
843     }
844     InitChessProgram(savCps, FALSE);
845     SendToProgram("force\n", savCps);
846     DisplayMessage("", "");
847     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
848     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
849     ThawUI();
850     SetGNUMode();
851 }
852
853 void
854 ReplaceEngine (ChessProgramState *cps, int n)
855 {
856     EditGameEvent();
857     UnloadEngine(cps);
858     appData.noChessProgram = FALSE;
859     appData.clockMode = TRUE;
860     InitEngine(cps, n);
861     UpdateLogos(TRUE);
862     if(n) return; // only startup first engine immediately; second can wait
863     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
864     LoadEngine();
865 }
866
867 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
868 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
869
870 static char resetOptions[] = 
871         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
872         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
873         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
874
875 void
876 Load (ChessProgramState *cps, int i)
877 {
878     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
879     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
880         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
881         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
882         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
883         ParseArgsFromString(buf);
884         SwapEngines(i);
885         ReplaceEngine(cps, i);
886         return;
887     }
888     p = engineName;
889     while(q = strchr(p, SLASH)) p = q+1;
890     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
891     if(engineDir[0] != NULLCHAR)
892         appData.directory[i] = engineDir;
893     else if(p != engineName) { // derive directory from engine path, when not given
894         p[-1] = 0;
895         appData.directory[i] = strdup(engineName);
896         p[-1] = SLASH;
897     } else appData.directory[i] = ".";
898     if(params[0]) {
899         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
900         snprintf(command, MSG_SIZ, "%s %s", p, params);
901         p = command;
902     }
903     appData.chessProgram[i] = strdup(p);
904     appData.isUCI[i] = isUCI;
905     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
906     appData.hasOwnBookUCI[i] = hasBook;
907     if(!nickName[0]) useNick = FALSE;
908     if(useNick) ASSIGN(appData.pgnName[i], nickName);
909     if(addToList) {
910         int len;
911         char quote;
912         q = firstChessProgramNames;
913         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
914         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
915         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
916                         quote, p, quote, appData.directory[i], 
917                         useNick ? " -fn \"" : "",
918                         useNick ? nickName : "",
919                         useNick ? "\"" : "",
920                         v1 ? " -firstProtocolVersion 1" : "",
921                         hasBook ? "" : " -fNoOwnBookUCI",
922                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
923                         storeVariant ? " -variant " : "",
924                         storeVariant ? VariantName(gameInfo.variant) : "");
925         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
926         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
927         if(q)   free(q);
928     }
929     ReplaceEngine(cps, i);
930 }
931
932 void
933 InitTimeControls ()
934 {
935     int matched, min, sec;
936     /*
937      * Parse timeControl resource
938      */
939     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
940                           appData.movesPerSession)) {
941         char buf[MSG_SIZ];
942         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
943         DisplayFatalError(buf, 0, 2);
944     }
945
946     /*
947      * Parse searchTime resource
948      */
949     if (*appData.searchTime != NULLCHAR) {
950         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
951         if (matched == 1) {
952             searchTime = min * 60;
953         } else if (matched == 2) {
954             searchTime = min * 60 + sec;
955         } else {
956             char buf[MSG_SIZ];
957             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
958             DisplayFatalError(buf, 0, 2);
959         }
960     }
961 }
962
963 void
964 InitBackEnd1 ()
965 {
966
967     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
968     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
969
970     GetTimeMark(&programStartTime);
971     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
972     appData.seedBase = random() + (random()<<15);
973     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
974
975     ClearProgramStats();
976     programStats.ok_to_send = 1;
977     programStats.seen_stat = 0;
978
979     /*
980      * Initialize game list
981      */
982     ListNew(&gameList);
983
984
985     /*
986      * Internet chess server status
987      */
988     if (appData.icsActive) {
989         appData.matchMode = FALSE;
990         appData.matchGames = 0;
991 #if ZIPPY
992         appData.noChessProgram = !appData.zippyPlay;
993 #else
994         appData.zippyPlay = FALSE;
995         appData.zippyTalk = FALSE;
996         appData.noChessProgram = TRUE;
997 #endif
998         if (*appData.icsHelper != NULLCHAR) {
999             appData.useTelnet = TRUE;
1000             appData.telnetProgram = appData.icsHelper;
1001         }
1002     } else {
1003         appData.zippyTalk = appData.zippyPlay = FALSE;
1004     }
1005
1006     /* [AS] Initialize pv info list [HGM] and game state */
1007     {
1008         int i, j;
1009
1010         for( i=0; i<=framePtr; i++ ) {
1011             pvInfoList[i].depth = -1;
1012             boards[i][EP_STATUS] = EP_NONE;
1013             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1014         }
1015     }
1016
1017     InitTimeControls();
1018
1019     /* [AS] Adjudication threshold */
1020     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1021
1022     InitEngine(&first, 0);
1023     InitEngine(&second, 1);
1024     CommonEngineInit();
1025
1026     pairing.which = "pairing"; // pairing engine
1027     pairing.pr = NoProc;
1028     pairing.isr = NULL;
1029     pairing.program = appData.pairingEngine;
1030     pairing.host = "localhost";
1031     pairing.dir = ".";
1032
1033     if (appData.icsActive) {
1034         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1035     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1036         appData.clockMode = FALSE;
1037         first.sendTime = second.sendTime = 0;
1038     }
1039
1040 #if ZIPPY
1041     /* Override some settings from environment variables, for backward
1042        compatibility.  Unfortunately it's not feasible to have the env
1043        vars just set defaults, at least in xboard.  Ugh.
1044     */
1045     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1046       ZippyInit();
1047     }
1048 #endif
1049
1050     if (!appData.icsActive) {
1051       char buf[MSG_SIZ];
1052       int len;
1053
1054       /* Check for variants that are supported only in ICS mode,
1055          or not at all.  Some that are accepted here nevertheless
1056          have bugs; see comments below.
1057       */
1058       VariantClass variant = StringToVariant(appData.variant);
1059       switch (variant) {
1060       case VariantBughouse:     /* need four players and two boards */
1061       case VariantKriegspiel:   /* need to hide pieces and move details */
1062         /* case VariantFischeRandom: (Fabien: moved below) */
1063         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1064         if( (len >= MSG_SIZ) && appData.debugMode )
1065           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1066
1067         DisplayFatalError(buf, 0, 2);
1068         return;
1069
1070       case VariantUnknown:
1071       case VariantLoadable:
1072       case Variant29:
1073       case Variant30:
1074       case Variant31:
1075       case Variant32:
1076       case Variant33:
1077       case Variant34:
1078       case Variant35:
1079       case Variant36:
1080       default:
1081         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1082         if( (len >= MSG_SIZ) && appData.debugMode )
1083           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1084
1085         DisplayFatalError(buf, 0, 2);
1086         return;
1087
1088       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1089       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1090       case VariantGothic:     /* [HGM] should work */
1091       case VariantCapablanca: /* [HGM] should work */
1092       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1093       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1094       case VariantKnightmate: /* [HGM] should work */
1095       case VariantCylinder:   /* [HGM] untested */
1096       case VariantFalcon:     /* [HGM] untested */
1097       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1098                                  offboard interposition not understood */
1099       case VariantNormal:     /* definitely works! */
1100       case VariantWildCastle: /* pieces not automatically shuffled */
1101       case VariantNoCastle:   /* pieces not automatically shuffled */
1102       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1103       case VariantLosers:     /* should work except for win condition,
1104                                  and doesn't know captures are mandatory */
1105       case VariantSuicide:    /* should work except for win condition,
1106                                  and doesn't know captures are mandatory */
1107       case VariantGiveaway:   /* should work except for win condition,
1108                                  and doesn't know captures are mandatory */
1109       case VariantTwoKings:   /* should work */
1110       case VariantAtomic:     /* should work except for win condition */
1111       case Variant3Check:     /* should work except for win condition */
1112       case VariantShatranj:   /* should work except for all win conditions */
1113       case VariantMakruk:     /* should work except for draw countdown */
1114       case VariantBerolina:   /* might work if TestLegality is off */
1115       case VariantCapaRandom: /* should work */
1116       case VariantJanus:      /* should work */
1117       case VariantSuper:      /* experimental */
1118       case VariantGreat:      /* experimental, requires legality testing to be off */
1119       case VariantSChess:     /* S-Chess, should work */
1120       case VariantGrand:      /* should work */
1121       case VariantSpartan:    /* should work */
1122         break;
1123       }
1124     }
1125
1126 }
1127
1128 int
1129 NextIntegerFromString (char ** str, long * value)
1130 {
1131     int result = -1;
1132     char * s = *str;
1133
1134     while( *s == ' ' || *s == '\t' ) {
1135         s++;
1136     }
1137
1138     *value = 0;
1139
1140     if( *s >= '0' && *s <= '9' ) {
1141         while( *s >= '0' && *s <= '9' ) {
1142             *value = *value * 10 + (*s - '0');
1143             s++;
1144         }
1145
1146         result = 0;
1147     }
1148
1149     *str = s;
1150
1151     return result;
1152 }
1153
1154 int
1155 NextTimeControlFromString (char ** str, long * value)
1156 {
1157     long temp;
1158     int result = NextIntegerFromString( str, &temp );
1159
1160     if( result == 0 ) {
1161         *value = temp * 60; /* Minutes */
1162         if( **str == ':' ) {
1163             (*str)++;
1164             result = NextIntegerFromString( str, &temp );
1165             *value += temp; /* Seconds */
1166         }
1167     }
1168
1169     return result;
1170 }
1171
1172 int
1173 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1174 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1175     int result = -1, type = 0; long temp, temp2;
1176
1177     if(**str != ':') return -1; // old params remain in force!
1178     (*str)++;
1179     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1180     if( NextIntegerFromString( str, &temp ) ) return -1;
1181     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1182
1183     if(**str != '/') {
1184         /* time only: incremental or sudden-death time control */
1185         if(**str == '+') { /* increment follows; read it */
1186             (*str)++;
1187             if(**str == '!') type = *(*str)++; // Bronstein TC
1188             if(result = NextIntegerFromString( str, &temp2)) return -1;
1189             *inc = temp2 * 1000;
1190             if(**str == '.') { // read fraction of increment
1191                 char *start = ++(*str);
1192                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1193                 temp2 *= 1000;
1194                 while(start++ < *str) temp2 /= 10;
1195                 *inc += temp2;
1196             }
1197         } else *inc = 0;
1198         *moves = 0; *tc = temp * 1000; *incType = type;
1199         return 0;
1200     }
1201
1202     (*str)++; /* classical time control */
1203     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1204
1205     if(result == 0) {
1206         *moves = temp;
1207         *tc    = temp2 * 1000;
1208         *inc   = 0;
1209         *incType = type;
1210     }
1211     return result;
1212 }
1213
1214 int
1215 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1216 {   /* [HGM] get time to add from the multi-session time-control string */
1217     int incType, moves=1; /* kludge to force reading of first session */
1218     long time, increment;
1219     char *s = tcString;
1220
1221     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1222     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1223     do {
1224         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1225         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1226         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1227         if(movenr == -1) return time;    /* last move before new session     */
1228         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1229         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1230         if(!moves) return increment;     /* current session is incremental   */
1231         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1232     } while(movenr >= -1);               /* try again for next session       */
1233
1234     return 0; // no new time quota on this move
1235 }
1236
1237 int
1238 ParseTimeControl (char *tc, float ti, int mps)
1239 {
1240   long tc1;
1241   long tc2;
1242   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1243   int min, sec=0;
1244
1245   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1246   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1247       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1248   if(ti > 0) {
1249
1250     if(mps)
1251       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1252     else 
1253       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1254   } else {
1255     if(mps)
1256       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1257     else 
1258       snprintf(buf, MSG_SIZ, ":%s", mytc);
1259   }
1260   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1261   
1262   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1263     return FALSE;
1264   }
1265
1266   if( *tc == '/' ) {
1267     /* Parse second time control */
1268     tc++;
1269
1270     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1271       return FALSE;
1272     }
1273
1274     if( tc2 == 0 ) {
1275       return FALSE;
1276     }
1277
1278     timeControl_2 = tc2 * 1000;
1279   }
1280   else {
1281     timeControl_2 = 0;
1282   }
1283
1284   if( tc1 == 0 ) {
1285     return FALSE;
1286   }
1287
1288   timeControl = tc1 * 1000;
1289
1290   if (ti >= 0) {
1291     timeIncrement = ti * 1000;  /* convert to ms */
1292     movesPerSession = 0;
1293   } else {
1294     timeIncrement = 0;
1295     movesPerSession = mps;
1296   }
1297   return TRUE;
1298 }
1299
1300 void
1301 InitBackEnd2 ()
1302 {
1303     if (appData.debugMode) {
1304         fprintf(debugFP, "%s\n", programVersion);
1305     }
1306
1307     set_cont_sequence(appData.wrapContSeq);
1308     if (appData.matchGames > 0) {
1309         appData.matchMode = TRUE;
1310     } else if (appData.matchMode) {
1311         appData.matchGames = 1;
1312     }
1313     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1314         appData.matchGames = appData.sameColorGames;
1315     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1316         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1317         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1318     }
1319     Reset(TRUE, FALSE);
1320     if (appData.noChessProgram || first.protocolVersion == 1) {
1321       InitBackEnd3();
1322     } else {
1323       /* kludge: allow timeout for initial "feature" commands */
1324       FreezeUI();
1325       DisplayMessage("", _("Starting chess program"));
1326       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1327     }
1328 }
1329
1330 int
1331 CalculateIndex (int index, int gameNr)
1332 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1333     int res;
1334     if(index > 0) return index; // fixed nmber
1335     if(index == 0) return 1;
1336     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1337     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1338     return res;
1339 }
1340
1341 int
1342 LoadGameOrPosition (int gameNr)
1343 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1344     if (*appData.loadGameFile != NULLCHAR) {
1345         if (!LoadGameFromFile(appData.loadGameFile,
1346                 CalculateIndex(appData.loadGameIndex, gameNr),
1347                               appData.loadGameFile, FALSE)) {
1348             DisplayFatalError(_("Bad game file"), 0, 1);
1349             return 0;
1350         }
1351     } else if (*appData.loadPositionFile != NULLCHAR) {
1352         if (!LoadPositionFromFile(appData.loadPositionFile,
1353                 CalculateIndex(appData.loadPositionIndex, gameNr),
1354                                   appData.loadPositionFile)) {
1355             DisplayFatalError(_("Bad position file"), 0, 1);
1356             return 0;
1357         }
1358     }
1359     return 1;
1360 }
1361
1362 void
1363 ReserveGame (int gameNr, char resChar)
1364 {
1365     FILE *tf = fopen(appData.tourneyFile, "r+");
1366     char *p, *q, c, buf[MSG_SIZ];
1367     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1368     safeStrCpy(buf, lastMsg, MSG_SIZ);
1369     DisplayMessage(_("Pick new game"), "");
1370     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1371     ParseArgsFromFile(tf);
1372     p = q = appData.results;
1373     if(appData.debugMode) {
1374       char *r = appData.participants;
1375       fprintf(debugFP, "results = '%s'\n", p);
1376       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1377       fprintf(debugFP, "\n");
1378     }
1379     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1380     nextGame = q - p;
1381     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1382     safeStrCpy(q, p, strlen(p) + 2);
1383     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1384     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1385     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1386         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1387         q[nextGame] = '*';
1388     }
1389     fseek(tf, -(strlen(p)+4), SEEK_END);
1390     c = fgetc(tf);
1391     if(c != '"') // depending on DOS or Unix line endings we can be one off
1392          fseek(tf, -(strlen(p)+2), SEEK_END);
1393     else fseek(tf, -(strlen(p)+3), SEEK_END);
1394     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1395     DisplayMessage(buf, "");
1396     free(p); appData.results = q;
1397     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1398        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1399         UnloadEngine(&first);  // next game belongs to other pairing;
1400         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1401     }
1402 }
1403
1404 void
1405 MatchEvent (int mode)
1406 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1407         int dummy;
1408         if(matchMode) { // already in match mode: switch it off
1409             abortMatch = TRUE;
1410             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1411             return;
1412         }
1413 //      if(gameMode != BeginningOfGame) {
1414 //          DisplayError(_("You can only start a match from the initial position."), 0);
1415 //          return;
1416 //      }
1417         abortMatch = FALSE;
1418         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1419         /* Set up machine vs. machine match */
1420         nextGame = 0;
1421         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1422         if(appData.tourneyFile[0]) {
1423             ReserveGame(-1, 0);
1424             if(nextGame > appData.matchGames) {
1425                 char buf[MSG_SIZ];
1426                 if(strchr(appData.results, '*') == NULL) {
1427                     FILE *f;
1428                     appData.tourneyCycles++;
1429                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1430                         fclose(f);
1431                         NextTourneyGame(-1, &dummy);
1432                         ReserveGame(-1, 0);
1433                         if(nextGame <= appData.matchGames) {
1434                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1435                             matchMode = mode;
1436                             ScheduleDelayedEvent(NextMatchGame, 10000);
1437                             return;
1438                         }
1439                     }
1440                 }
1441                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1442                 DisplayError(buf, 0);
1443                 appData.tourneyFile[0] = 0;
1444                 return;
1445             }
1446         } else
1447         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1448             DisplayFatalError(_("Can't have a match with no chess programs"),
1449                               0, 2);
1450             return;
1451         }
1452         matchMode = mode;
1453         matchGame = roundNr = 1;
1454         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1455         NextMatchGame();
1456 }
1457
1458 void
1459 InitBackEnd3 P((void))
1460 {
1461     GameMode initialMode;
1462     char buf[MSG_SIZ];
1463     int err, len;
1464
1465     InitChessProgram(&first, startedFromSetupPosition);
1466
1467     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1468         free(programVersion);
1469         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1470         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1471     }
1472
1473     if (appData.icsActive) {
1474 #ifdef WIN32
1475         /* [DM] Make a console window if needed [HGM] merged ifs */
1476         ConsoleCreate();
1477 #endif
1478         err = establish();
1479         if (err != 0)
1480           {
1481             if (*appData.icsCommPort != NULLCHAR)
1482               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1483                              appData.icsCommPort);
1484             else
1485               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1486                         appData.icsHost, appData.icsPort);
1487
1488             if( (len >= MSG_SIZ) && appData.debugMode )
1489               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1490
1491             DisplayFatalError(buf, err, 1);
1492             return;
1493         }
1494         SetICSMode();
1495         telnetISR =
1496           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1497         fromUserISR =
1498           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1499         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1500             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1501     } else if (appData.noChessProgram) {
1502         SetNCPMode();
1503     } else {
1504         SetGNUMode();
1505     }
1506
1507     if (*appData.cmailGameName != NULLCHAR) {
1508         SetCmailMode();
1509         OpenLoopback(&cmailPR);
1510         cmailISR =
1511           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1512     }
1513
1514     ThawUI();
1515     DisplayMessage("", "");
1516     if (StrCaseCmp(appData.initialMode, "") == 0) {
1517       initialMode = BeginningOfGame;
1518       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1519         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1520         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1521         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1522         ModeHighlight();
1523       }
1524     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1525       initialMode = TwoMachinesPlay;
1526     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1527       initialMode = AnalyzeFile;
1528     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1529       initialMode = AnalyzeMode;
1530     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1531       initialMode = MachinePlaysWhite;
1532     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1533       initialMode = MachinePlaysBlack;
1534     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1535       initialMode = EditGame;
1536     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1537       initialMode = EditPosition;
1538     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1539       initialMode = Training;
1540     } else {
1541       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1542       if( (len >= MSG_SIZ) && appData.debugMode )
1543         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1544
1545       DisplayFatalError(buf, 0, 2);
1546       return;
1547     }
1548
1549     if (appData.matchMode) {
1550         if(appData.tourneyFile[0]) { // start tourney from command line
1551             FILE *f;
1552             if(f = fopen(appData.tourneyFile, "r")) {
1553                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1554                 fclose(f);
1555                 appData.clockMode = TRUE;
1556                 SetGNUMode();
1557             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1558         }
1559         MatchEvent(TRUE);
1560     } else if (*appData.cmailGameName != NULLCHAR) {
1561         /* Set up cmail mode */
1562         ReloadCmailMsgEvent(TRUE);
1563     } else {
1564         /* Set up other modes */
1565         if (initialMode == AnalyzeFile) {
1566           if (*appData.loadGameFile == NULLCHAR) {
1567             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1568             return;
1569           }
1570         }
1571         if (*appData.loadGameFile != NULLCHAR) {
1572             (void) LoadGameFromFile(appData.loadGameFile,
1573                                     appData.loadGameIndex,
1574                                     appData.loadGameFile, TRUE);
1575         } else if (*appData.loadPositionFile != NULLCHAR) {
1576             (void) LoadPositionFromFile(appData.loadPositionFile,
1577                                         appData.loadPositionIndex,
1578                                         appData.loadPositionFile);
1579             /* [HGM] try to make self-starting even after FEN load */
1580             /* to allow automatic setup of fairy variants with wtm */
1581             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1582                 gameMode = BeginningOfGame;
1583                 setboardSpoiledMachineBlack = 1;
1584             }
1585             /* [HGM] loadPos: make that every new game uses the setup */
1586             /* from file as long as we do not switch variant          */
1587             if(!blackPlaysFirst) {
1588                 startedFromPositionFile = TRUE;
1589                 CopyBoard(filePosition, boards[0]);
1590             }
1591         }
1592         if (initialMode == AnalyzeMode) {
1593           if (appData.noChessProgram) {
1594             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1595             return;
1596           }
1597           if (appData.icsActive) {
1598             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1599             return;
1600           }
1601           AnalyzeModeEvent();
1602         } else if (initialMode == AnalyzeFile) {
1603           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1604           ShowThinkingEvent();
1605           AnalyzeFileEvent();
1606           AnalysisPeriodicEvent(1);
1607         } else if (initialMode == MachinePlaysWhite) {
1608           if (appData.noChessProgram) {
1609             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1610                               0, 2);
1611             return;
1612           }
1613           if (appData.icsActive) {
1614             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1615                               0, 2);
1616             return;
1617           }
1618           MachineWhiteEvent();
1619         } else if (initialMode == MachinePlaysBlack) {
1620           if (appData.noChessProgram) {
1621             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1622                               0, 2);
1623             return;
1624           }
1625           if (appData.icsActive) {
1626             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1627                               0, 2);
1628             return;
1629           }
1630           MachineBlackEvent();
1631         } else if (initialMode == TwoMachinesPlay) {
1632           if (appData.noChessProgram) {
1633             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1634                               0, 2);
1635             return;
1636           }
1637           if (appData.icsActive) {
1638             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1639                               0, 2);
1640             return;
1641           }
1642           TwoMachinesEvent();
1643         } else if (initialMode == EditGame) {
1644           EditGameEvent();
1645         } else if (initialMode == EditPosition) {
1646           EditPositionEvent();
1647         } else if (initialMode == Training) {
1648           if (*appData.loadGameFile == NULLCHAR) {
1649             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1650             return;
1651           }
1652           TrainingEvent();
1653         }
1654     }
1655 }
1656
1657 void
1658 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1659 {
1660     DisplayBook(current+1);
1661
1662     MoveHistorySet( movelist, first, last, current, pvInfoList );
1663
1664     EvalGraphSet( first, last, current, pvInfoList );
1665
1666     MakeEngineOutputTitle();
1667 }
1668
1669 /*
1670  * Establish will establish a contact to a remote host.port.
1671  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1672  *  used to talk to the host.
1673  * Returns 0 if okay, error code if not.
1674  */
1675 int
1676 establish ()
1677 {
1678     char buf[MSG_SIZ];
1679
1680     if (*appData.icsCommPort != NULLCHAR) {
1681         /* Talk to the host through a serial comm port */
1682         return OpenCommPort(appData.icsCommPort, &icsPR);
1683
1684     } else if (*appData.gateway != NULLCHAR) {
1685         if (*appData.remoteShell == NULLCHAR) {
1686             /* Use the rcmd protocol to run telnet program on a gateway host */
1687             snprintf(buf, sizeof(buf), "%s %s %s",
1688                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1689             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1690
1691         } else {
1692             /* Use the rsh program to run telnet program on a gateway host */
1693             if (*appData.remoteUser == NULLCHAR) {
1694                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1695                         appData.gateway, appData.telnetProgram,
1696                         appData.icsHost, appData.icsPort);
1697             } else {
1698                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1699                         appData.remoteShell, appData.gateway,
1700                         appData.remoteUser, appData.telnetProgram,
1701                         appData.icsHost, appData.icsPort);
1702             }
1703             return StartChildProcess(buf, "", &icsPR);
1704
1705         }
1706     } else if (appData.useTelnet) {
1707         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1708
1709     } else {
1710         /* TCP socket interface differs somewhat between
1711            Unix and NT; handle details in the front end.
1712            */
1713         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1714     }
1715 }
1716
1717 void
1718 EscapeExpand (char *p, char *q)
1719 {       // [HGM] initstring: routine to shape up string arguments
1720         while(*p++ = *q++) if(p[-1] == '\\')
1721             switch(*q++) {
1722                 case 'n': p[-1] = '\n'; break;
1723                 case 'r': p[-1] = '\r'; break;
1724                 case 't': p[-1] = '\t'; break;
1725                 case '\\': p[-1] = '\\'; break;
1726                 case 0: *p = 0; return;
1727                 default: p[-1] = q[-1]; break;
1728             }
1729 }
1730
1731 void
1732 show_bytes (FILE *fp, char *buf, int count)
1733 {
1734     while (count--) {
1735         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1736             fprintf(fp, "\\%03o", *buf & 0xff);
1737         } else {
1738             putc(*buf, fp);
1739         }
1740         buf++;
1741     }
1742     fflush(fp);
1743 }
1744
1745 /* Returns an errno value */
1746 int
1747 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1748 {
1749     char buf[8192], *p, *q, *buflim;
1750     int left, newcount, outcount;
1751
1752     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1753         *appData.gateway != NULLCHAR) {
1754         if (appData.debugMode) {
1755             fprintf(debugFP, ">ICS: ");
1756             show_bytes(debugFP, message, count);
1757             fprintf(debugFP, "\n");
1758         }
1759         return OutputToProcess(pr, message, count, outError);
1760     }
1761
1762     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1763     p = message;
1764     q = buf;
1765     left = count;
1766     newcount = 0;
1767     while (left) {
1768         if (q >= buflim) {
1769             if (appData.debugMode) {
1770                 fprintf(debugFP, ">ICS: ");
1771                 show_bytes(debugFP, buf, newcount);
1772                 fprintf(debugFP, "\n");
1773             }
1774             outcount = OutputToProcess(pr, buf, newcount, outError);
1775             if (outcount < newcount) return -1; /* to be sure */
1776             q = buf;
1777             newcount = 0;
1778         }
1779         if (*p == '\n') {
1780             *q++ = '\r';
1781             newcount++;
1782         } else if (((unsigned char) *p) == TN_IAC) {
1783             *q++ = (char) TN_IAC;
1784             newcount ++;
1785         }
1786         *q++ = *p++;
1787         newcount++;
1788         left--;
1789     }
1790     if (appData.debugMode) {
1791         fprintf(debugFP, ">ICS: ");
1792         show_bytes(debugFP, buf, newcount);
1793         fprintf(debugFP, "\n");
1794     }
1795     outcount = OutputToProcess(pr, buf, newcount, outError);
1796     if (outcount < newcount) return -1; /* to be sure */
1797     return count;
1798 }
1799
1800 void
1801 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1802 {
1803     int outError, outCount;
1804     static int gotEof = 0;
1805
1806     /* Pass data read from player on to ICS */
1807     if (count > 0) {
1808         gotEof = 0;
1809         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1810         if (outCount < count) {
1811             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1812         }
1813     } else if (count < 0) {
1814         RemoveInputSource(isr);
1815         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1816     } else if (gotEof++ > 0) {
1817         RemoveInputSource(isr);
1818         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1819     }
1820 }
1821
1822 void
1823 KeepAlive ()
1824 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1825     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1826     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1827     SendToICS("date\n");
1828     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1829 }
1830
1831 /* added routine for printf style output to ics */
1832 void
1833 ics_printf (char *format, ...)
1834 {
1835     char buffer[MSG_SIZ];
1836     va_list args;
1837
1838     va_start(args, format);
1839     vsnprintf(buffer, sizeof(buffer), format, args);
1840     buffer[sizeof(buffer)-1] = '\0';
1841     SendToICS(buffer);
1842     va_end(args);
1843 }
1844
1845 void
1846 SendToICS (char *s)
1847 {
1848     int count, outCount, outError;
1849
1850     if (icsPR == NoProc) return;
1851
1852     count = strlen(s);
1853     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1854     if (outCount < count) {
1855         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1856     }
1857 }
1858
1859 /* This is used for sending logon scripts to the ICS. Sending
1860    without a delay causes problems when using timestamp on ICC
1861    (at least on my machine). */
1862 void
1863 SendToICSDelayed (char *s, long msdelay)
1864 {
1865     int count, outCount, outError;
1866
1867     if (icsPR == NoProc) return;
1868
1869     count = strlen(s);
1870     if (appData.debugMode) {
1871         fprintf(debugFP, ">ICS: ");
1872         show_bytes(debugFP, s, count);
1873         fprintf(debugFP, "\n");
1874     }
1875     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1876                                       msdelay);
1877     if (outCount < count) {
1878         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1879     }
1880 }
1881
1882
1883 /* Remove all highlighting escape sequences in s
1884    Also deletes any suffix starting with '('
1885    */
1886 char *
1887 StripHighlightAndTitle (char *s)
1888 {
1889     static char retbuf[MSG_SIZ];
1890     char *p = retbuf;
1891
1892     while (*s != NULLCHAR) {
1893         while (*s == '\033') {
1894             while (*s != NULLCHAR && !isalpha(*s)) s++;
1895             if (*s != NULLCHAR) s++;
1896         }
1897         while (*s != NULLCHAR && *s != '\033') {
1898             if (*s == '(' || *s == '[') {
1899                 *p = NULLCHAR;
1900                 return retbuf;
1901             }
1902             *p++ = *s++;
1903         }
1904     }
1905     *p = NULLCHAR;
1906     return retbuf;
1907 }
1908
1909 /* Remove all highlighting escape sequences in s */
1910 char *
1911 StripHighlight (char *s)
1912 {
1913     static char retbuf[MSG_SIZ];
1914     char *p = retbuf;
1915
1916     while (*s != NULLCHAR) {
1917         while (*s == '\033') {
1918             while (*s != NULLCHAR && !isalpha(*s)) s++;
1919             if (*s != NULLCHAR) s++;
1920         }
1921         while (*s != NULLCHAR && *s != '\033') {
1922             *p++ = *s++;
1923         }
1924     }
1925     *p = NULLCHAR;
1926     return retbuf;
1927 }
1928
1929 char *variantNames[] = VARIANT_NAMES;
1930 char *
1931 VariantName (VariantClass v)
1932 {
1933     return variantNames[v];
1934 }
1935
1936
1937 /* Identify a variant from the strings the chess servers use or the
1938    PGN Variant tag names we use. */
1939 VariantClass
1940 StringToVariant (char *e)
1941 {
1942     char *p;
1943     int wnum = -1;
1944     VariantClass v = VariantNormal;
1945     int i, found = FALSE;
1946     char buf[MSG_SIZ];
1947     int len;
1948
1949     if (!e) return v;
1950
1951     /* [HGM] skip over optional board-size prefixes */
1952     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1953         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1954         while( *e++ != '_');
1955     }
1956
1957     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1958         v = VariantNormal;
1959         found = TRUE;
1960     } else
1961     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1962       if (StrCaseStr(e, variantNames[i])) {
1963         v = (VariantClass) i;
1964         found = TRUE;
1965         break;
1966       }
1967     }
1968
1969     if (!found) {
1970       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1971           || StrCaseStr(e, "wild/fr")
1972           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1973         v = VariantFischeRandom;
1974       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1975                  (i = 1, p = StrCaseStr(e, "w"))) {
1976         p += i;
1977         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1978         if (isdigit(*p)) {
1979           wnum = atoi(p);
1980         } else {
1981           wnum = -1;
1982         }
1983         switch (wnum) {
1984         case 0: /* FICS only, actually */
1985         case 1:
1986           /* Castling legal even if K starts on d-file */
1987           v = VariantWildCastle;
1988           break;
1989         case 2:
1990         case 3:
1991         case 4:
1992           /* Castling illegal even if K & R happen to start in
1993              normal positions. */
1994           v = VariantNoCastle;
1995           break;
1996         case 5:
1997         case 7:
1998         case 8:
1999         case 10:
2000         case 11:
2001         case 12:
2002         case 13:
2003         case 14:
2004         case 15:
2005         case 18:
2006         case 19:
2007           /* Castling legal iff K & R start in normal positions */
2008           v = VariantNormal;
2009           break;
2010         case 6:
2011         case 20:
2012         case 21:
2013           /* Special wilds for position setup; unclear what to do here */
2014           v = VariantLoadable;
2015           break;
2016         case 9:
2017           /* Bizarre ICC game */
2018           v = VariantTwoKings;
2019           break;
2020         case 16:
2021           v = VariantKriegspiel;
2022           break;
2023         case 17:
2024           v = VariantLosers;
2025           break;
2026         case 22:
2027           v = VariantFischeRandom;
2028           break;
2029         case 23:
2030           v = VariantCrazyhouse;
2031           break;
2032         case 24:
2033           v = VariantBughouse;
2034           break;
2035         case 25:
2036           v = Variant3Check;
2037           break;
2038         case 26:
2039           /* Not quite the same as FICS suicide! */
2040           v = VariantGiveaway;
2041           break;
2042         case 27:
2043           v = VariantAtomic;
2044           break;
2045         case 28:
2046           v = VariantShatranj;
2047           break;
2048
2049         /* Temporary names for future ICC types.  The name *will* change in
2050            the next xboard/WinBoard release after ICC defines it. */
2051         case 29:
2052           v = Variant29;
2053           break;
2054         case 30:
2055           v = Variant30;
2056           break;
2057         case 31:
2058           v = Variant31;
2059           break;
2060         case 32:
2061           v = Variant32;
2062           break;
2063         case 33:
2064           v = Variant33;
2065           break;
2066         case 34:
2067           v = Variant34;
2068           break;
2069         case 35:
2070           v = Variant35;
2071           break;
2072         case 36:
2073           v = Variant36;
2074           break;
2075         case 37:
2076           v = VariantShogi;
2077           break;
2078         case 38:
2079           v = VariantXiangqi;
2080           break;
2081         case 39:
2082           v = VariantCourier;
2083           break;
2084         case 40:
2085           v = VariantGothic;
2086           break;
2087         case 41:
2088           v = VariantCapablanca;
2089           break;
2090         case 42:
2091           v = VariantKnightmate;
2092           break;
2093         case 43:
2094           v = VariantFairy;
2095           break;
2096         case 44:
2097           v = VariantCylinder;
2098           break;
2099         case 45:
2100           v = VariantFalcon;
2101           break;
2102         case 46:
2103           v = VariantCapaRandom;
2104           break;
2105         case 47:
2106           v = VariantBerolina;
2107           break;
2108         case 48:
2109           v = VariantJanus;
2110           break;
2111         case 49:
2112           v = VariantSuper;
2113           break;
2114         case 50:
2115           v = VariantGreat;
2116           break;
2117         case -1:
2118           /* Found "wild" or "w" in the string but no number;
2119              must assume it's normal chess. */
2120           v = VariantNormal;
2121           break;
2122         default:
2123           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2124           if( (len >= MSG_SIZ) && appData.debugMode )
2125             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2126
2127           DisplayError(buf, 0);
2128           v = VariantUnknown;
2129           break;
2130         }
2131       }
2132     }
2133     if (appData.debugMode) {
2134       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2135               e, wnum, VariantName(v));
2136     }
2137     return v;
2138 }
2139
2140 static int leftover_start = 0, leftover_len = 0;
2141 char star_match[STAR_MATCH_N][MSG_SIZ];
2142
2143 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2144    advance *index beyond it, and set leftover_start to the new value of
2145    *index; else return FALSE.  If pattern contains the character '*', it
2146    matches any sequence of characters not containing '\r', '\n', or the
2147    character following the '*' (if any), and the matched sequence(s) are
2148    copied into star_match.
2149    */
2150 int
2151 looking_at ( char *buf, int *index, char *pattern)
2152 {
2153     char *bufp = &buf[*index], *patternp = pattern;
2154     int star_count = 0;
2155     char *matchp = star_match[0];
2156
2157     for (;;) {
2158         if (*patternp == NULLCHAR) {
2159             *index = leftover_start = bufp - buf;
2160             *matchp = NULLCHAR;
2161             return TRUE;
2162         }
2163         if (*bufp == NULLCHAR) return FALSE;
2164         if (*patternp == '*') {
2165             if (*bufp == *(patternp + 1)) {
2166                 *matchp = NULLCHAR;
2167                 matchp = star_match[++star_count];
2168                 patternp += 2;
2169                 bufp++;
2170                 continue;
2171             } else if (*bufp == '\n' || *bufp == '\r') {
2172                 patternp++;
2173                 if (*patternp == NULLCHAR)
2174                   continue;
2175                 else
2176                   return FALSE;
2177             } else {
2178                 *matchp++ = *bufp++;
2179                 continue;
2180             }
2181         }
2182         if (*patternp != *bufp) return FALSE;
2183         patternp++;
2184         bufp++;
2185     }
2186 }
2187
2188 void
2189 SendToPlayer (char *data, int length)
2190 {
2191     int error, outCount;
2192     outCount = OutputToProcess(NoProc, data, length, &error);
2193     if (outCount < length) {
2194         DisplayFatalError(_("Error writing to display"), error, 1);
2195     }
2196 }
2197
2198 void
2199 PackHolding (char packed[], char *holding)
2200 {
2201     char *p = holding;
2202     char *q = packed;
2203     int runlength = 0;
2204     int curr = 9999;
2205     do {
2206         if (*p == curr) {
2207             runlength++;
2208         } else {
2209             switch (runlength) {
2210               case 0:
2211                 break;
2212               case 1:
2213                 *q++ = curr;
2214                 break;
2215               case 2:
2216                 *q++ = curr;
2217                 *q++ = curr;
2218                 break;
2219               default:
2220                 sprintf(q, "%d", runlength);
2221                 while (*q) q++;
2222                 *q++ = curr;
2223                 break;
2224             }
2225             runlength = 1;
2226             curr = *p;
2227         }
2228     } while (*p++);
2229     *q = NULLCHAR;
2230 }
2231
2232 /* Telnet protocol requests from the front end */
2233 void
2234 TelnetRequest (unsigned char ddww, unsigned char option)
2235 {
2236     unsigned char msg[3];
2237     int outCount, outError;
2238
2239     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2240
2241     if (appData.debugMode) {
2242         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2243         switch (ddww) {
2244           case TN_DO:
2245             ddwwStr = "DO";
2246             break;
2247           case TN_DONT:
2248             ddwwStr = "DONT";
2249             break;
2250           case TN_WILL:
2251             ddwwStr = "WILL";
2252             break;
2253           case TN_WONT:
2254             ddwwStr = "WONT";
2255             break;
2256           default:
2257             ddwwStr = buf1;
2258             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2259             break;
2260         }
2261         switch (option) {
2262           case TN_ECHO:
2263             optionStr = "ECHO";
2264             break;
2265           default:
2266             optionStr = buf2;
2267             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2268             break;
2269         }
2270         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2271     }
2272     msg[0] = TN_IAC;
2273     msg[1] = ddww;
2274     msg[2] = option;
2275     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2276     if (outCount < 3) {
2277         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2278     }
2279 }
2280
2281 void
2282 DoEcho ()
2283 {
2284     if (!appData.icsActive) return;
2285     TelnetRequest(TN_DO, TN_ECHO);
2286 }
2287
2288 void
2289 DontEcho ()
2290 {
2291     if (!appData.icsActive) return;
2292     TelnetRequest(TN_DONT, TN_ECHO);
2293 }
2294
2295 void
2296 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2297 {
2298     /* put the holdings sent to us by the server on the board holdings area */
2299     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2300     char p;
2301     ChessSquare piece;
2302
2303     if(gameInfo.holdingsWidth < 2)  return;
2304     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2305         return; // prevent overwriting by pre-board holdings
2306
2307     if( (int)lowestPiece >= BlackPawn ) {
2308         holdingsColumn = 0;
2309         countsColumn = 1;
2310         holdingsStartRow = BOARD_HEIGHT-1;
2311         direction = -1;
2312     } else {
2313         holdingsColumn = BOARD_WIDTH-1;
2314         countsColumn = BOARD_WIDTH-2;
2315         holdingsStartRow = 0;
2316         direction = 1;
2317     }
2318
2319     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2320         board[i][holdingsColumn] = EmptySquare;
2321         board[i][countsColumn]   = (ChessSquare) 0;
2322     }
2323     while( (p=*holdings++) != NULLCHAR ) {
2324         piece = CharToPiece( ToUpper(p) );
2325         if(piece == EmptySquare) continue;
2326         /*j = (int) piece - (int) WhitePawn;*/
2327         j = PieceToNumber(piece);
2328         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2329         if(j < 0) continue;               /* should not happen */
2330         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2331         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2332         board[holdingsStartRow+j*direction][countsColumn]++;
2333     }
2334 }
2335
2336
2337 void
2338 VariantSwitch (Board board, VariantClass newVariant)
2339 {
2340    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2341    static Board oldBoard;
2342
2343    startedFromPositionFile = FALSE;
2344    if(gameInfo.variant == newVariant) return;
2345
2346    /* [HGM] This routine is called each time an assignment is made to
2347     * gameInfo.variant during a game, to make sure the board sizes
2348     * are set to match the new variant. If that means adding or deleting
2349     * holdings, we shift the playing board accordingly
2350     * This kludge is needed because in ICS observe mode, we get boards
2351     * of an ongoing game without knowing the variant, and learn about the
2352     * latter only later. This can be because of the move list we requested,
2353     * in which case the game history is refilled from the beginning anyway,
2354     * but also when receiving holdings of a crazyhouse game. In the latter
2355     * case we want to add those holdings to the already received position.
2356     */
2357
2358
2359    if (appData.debugMode) {
2360      fprintf(debugFP, "Switch board from %s to %s\n",
2361              VariantName(gameInfo.variant), VariantName(newVariant));
2362      setbuf(debugFP, NULL);
2363    }
2364    shuffleOpenings = 0;       /* [HGM] shuffle */
2365    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2366    switch(newVariant)
2367      {
2368      case VariantShogi:
2369        newWidth = 9;  newHeight = 9;
2370        gameInfo.holdingsSize = 7;
2371      case VariantBughouse:
2372      case VariantCrazyhouse:
2373        newHoldingsWidth = 2; break;
2374      case VariantGreat:
2375        newWidth = 10;
2376      case VariantSuper:
2377        newHoldingsWidth = 2;
2378        gameInfo.holdingsSize = 8;
2379        break;
2380      case VariantGothic:
2381      case VariantCapablanca:
2382      case VariantCapaRandom:
2383        newWidth = 10;
2384      default:
2385        newHoldingsWidth = gameInfo.holdingsSize = 0;
2386      };
2387
2388    if(newWidth  != gameInfo.boardWidth  ||
2389       newHeight != gameInfo.boardHeight ||
2390       newHoldingsWidth != gameInfo.holdingsWidth ) {
2391
2392      /* shift position to new playing area, if needed */
2393      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2394        for(i=0; i<BOARD_HEIGHT; i++)
2395          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2396            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2397              board[i][j];
2398        for(i=0; i<newHeight; i++) {
2399          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2400          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2401        }
2402      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2403        for(i=0; i<BOARD_HEIGHT; i++)
2404          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2405            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2406              board[i][j];
2407      }
2408      gameInfo.boardWidth  = newWidth;
2409      gameInfo.boardHeight = newHeight;
2410      gameInfo.holdingsWidth = newHoldingsWidth;
2411      gameInfo.variant = newVariant;
2412      InitDrawingSizes(-2, 0);
2413    } else gameInfo.variant = newVariant;
2414    CopyBoard(oldBoard, board);   // remember correctly formatted board
2415      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2416    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2417 }
2418
2419 static int loggedOn = FALSE;
2420
2421 /*-- Game start info cache: --*/
2422 int gs_gamenum;
2423 char gs_kind[MSG_SIZ];
2424 static char player1Name[128] = "";
2425 static char player2Name[128] = "";
2426 static char cont_seq[] = "\n\\   ";
2427 static int player1Rating = -1;
2428 static int player2Rating = -1;
2429 /*----------------------------*/
2430
2431 ColorClass curColor = ColorNormal;
2432 int suppressKibitz = 0;
2433
2434 // [HGM] seekgraph
2435 Boolean soughtPending = FALSE;
2436 Boolean seekGraphUp;
2437 #define MAX_SEEK_ADS 200
2438 #define SQUARE 0x80
2439 char *seekAdList[MAX_SEEK_ADS];
2440 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2441 float tcList[MAX_SEEK_ADS];
2442 char colorList[MAX_SEEK_ADS];
2443 int nrOfSeekAds = 0;
2444 int minRating = 1010, maxRating = 2800;
2445 int hMargin = 10, vMargin = 20, h, w;
2446 extern int squareSize, lineGap;
2447
2448 void
2449 PlotSeekAd (int i)
2450 {
2451         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2452         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2453         if(r < minRating+100 && r >=0 ) r = minRating+100;
2454         if(r > maxRating) r = maxRating;
2455         if(tc < 1.) tc = 1.;
2456         if(tc > 95.) tc = 95.;
2457         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2458         y = ((double)r - minRating)/(maxRating - minRating)
2459             * (h-vMargin-squareSize/8-1) + vMargin;
2460         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2461         if(strstr(seekAdList[i], " u ")) color = 1;
2462         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2463            !strstr(seekAdList[i], "bullet") &&
2464            !strstr(seekAdList[i], "blitz") &&
2465            !strstr(seekAdList[i], "standard") ) color = 2;
2466         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2467         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2468 }
2469
2470 void
2471 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2472 {
2473         char buf[MSG_SIZ], *ext = "";
2474         VariantClass v = StringToVariant(type);
2475         if(strstr(type, "wild")) {
2476             ext = type + 4; // append wild number
2477             if(v == VariantFischeRandom) type = "chess960"; else
2478             if(v == VariantLoadable) type = "setup"; else
2479             type = VariantName(v);
2480         }
2481         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2482         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2483             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2484             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2485             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2486             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2487             seekNrList[nrOfSeekAds] = nr;
2488             zList[nrOfSeekAds] = 0;
2489             seekAdList[nrOfSeekAds++] = StrSave(buf);
2490             if(plot) PlotSeekAd(nrOfSeekAds-1);
2491         }
2492 }
2493
2494 void
2495 EraseSeekDot (int i)
2496 {
2497     int x = xList[i], y = yList[i], d=squareSize/4, k;
2498     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2499     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2500     // now replot every dot that overlapped
2501     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2502         int xx = xList[k], yy = yList[k];
2503         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2504             DrawSeekDot(xx, yy, colorList[k]);
2505     }
2506 }
2507
2508 void
2509 RemoveSeekAd (int nr)
2510 {
2511         int i;
2512         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2513             EraseSeekDot(i);
2514             if(seekAdList[i]) free(seekAdList[i]);
2515             seekAdList[i] = seekAdList[--nrOfSeekAds];
2516             seekNrList[i] = seekNrList[nrOfSeekAds];
2517             ratingList[i] = ratingList[nrOfSeekAds];
2518             colorList[i]  = colorList[nrOfSeekAds];
2519             tcList[i] = tcList[nrOfSeekAds];
2520             xList[i]  = xList[nrOfSeekAds];
2521             yList[i]  = yList[nrOfSeekAds];
2522             zList[i]  = zList[nrOfSeekAds];
2523             seekAdList[nrOfSeekAds] = NULL;
2524             break;
2525         }
2526 }
2527
2528 Boolean
2529 MatchSoughtLine (char *line)
2530 {
2531     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2532     int nr, base, inc, u=0; char dummy;
2533
2534     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2535        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2536        (u=1) &&
2537        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2538         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2539         // match: compact and save the line
2540         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2541         return TRUE;
2542     }
2543     return FALSE;
2544 }
2545
2546 int
2547 DrawSeekGraph ()
2548 {
2549     int i;
2550     if(!seekGraphUp) return FALSE;
2551     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2552     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2553
2554     DrawSeekBackground(0, 0, w, h);
2555     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2556     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2557     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2558         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2559         yy = h-1-yy;
2560         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2561         if(i%500 == 0) {
2562             char buf[MSG_SIZ];
2563             snprintf(buf, MSG_SIZ, "%d", i);
2564             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2565         }
2566     }
2567     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2568     for(i=1; i<100; i+=(i<10?1:5)) {
2569         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2570         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2571         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2572             char buf[MSG_SIZ];
2573             snprintf(buf, MSG_SIZ, "%d", i);
2574             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2575         }
2576     }
2577     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2578     return TRUE;
2579 }
2580
2581 int
2582 SeekGraphClick (ClickType click, int x, int y, int moving)
2583 {
2584     static int lastDown = 0, displayed = 0, lastSecond;
2585     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2586         if(click == Release || moving) return FALSE;
2587         nrOfSeekAds = 0;
2588         soughtPending = TRUE;
2589         SendToICS(ics_prefix);
2590         SendToICS("sought\n"); // should this be "sought all"?
2591     } else { // issue challenge based on clicked ad
2592         int dist = 10000; int i, closest = 0, second = 0;
2593         for(i=0; i<nrOfSeekAds; i++) {
2594             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2595             if(d < dist) { dist = d; closest = i; }
2596             second += (d - zList[i] < 120); // count in-range ads
2597             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2598         }
2599         if(dist < 120) {
2600             char buf[MSG_SIZ];
2601             second = (second > 1);
2602             if(displayed != closest || second != lastSecond) {
2603                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2604                 lastSecond = second; displayed = closest;
2605             }
2606             if(click == Press) {
2607                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2608                 lastDown = closest;
2609                 return TRUE;
2610             } // on press 'hit', only show info
2611             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2612             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2613             SendToICS(ics_prefix);
2614             SendToICS(buf);
2615             return TRUE; // let incoming board of started game pop down the graph
2616         } else if(click == Release) { // release 'miss' is ignored
2617             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2618             if(moving == 2) { // right up-click
2619                 nrOfSeekAds = 0; // refresh graph
2620                 soughtPending = TRUE;
2621                 SendToICS(ics_prefix);
2622                 SendToICS("sought\n"); // should this be "sought all"?
2623             }
2624             return TRUE;
2625         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2626         // press miss or release hit 'pop down' seek graph
2627         seekGraphUp = FALSE;
2628         DrawPosition(TRUE, NULL);
2629     }
2630     return TRUE;
2631 }
2632
2633 void
2634 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2635 {
2636 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2637 #define STARTED_NONE 0
2638 #define STARTED_MOVES 1
2639 #define STARTED_BOARD 2
2640 #define STARTED_OBSERVE 3
2641 #define STARTED_HOLDINGS 4
2642 #define STARTED_CHATTER 5
2643 #define STARTED_COMMENT 6
2644 #define STARTED_MOVES_NOHIDE 7
2645
2646     static int started = STARTED_NONE;
2647     static char parse[20000];
2648     static int parse_pos = 0;
2649     static char buf[BUF_SIZE + 1];
2650     static int firstTime = TRUE, intfSet = FALSE;
2651     static ColorClass prevColor = ColorNormal;
2652     static int savingComment = FALSE;
2653     static int cmatch = 0; // continuation sequence match
2654     char *bp;
2655     char str[MSG_SIZ];
2656     int i, oldi;
2657     int buf_len;
2658     int next_out;
2659     int tkind;
2660     int backup;    /* [DM] For zippy color lines */
2661     char *p;
2662     char talker[MSG_SIZ]; // [HGM] chat
2663     int channel;
2664
2665     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2666
2667     if (appData.debugMode) {
2668       if (!error) {
2669         fprintf(debugFP, "<ICS: ");
2670         show_bytes(debugFP, data, count);
2671         fprintf(debugFP, "\n");
2672       }
2673     }
2674
2675     if (appData.debugMode) { int f = forwardMostMove;
2676         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2677                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2678                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2679     }
2680     if (count > 0) {
2681         /* If last read ended with a partial line that we couldn't parse,
2682            prepend it to the new read and try again. */
2683         if (leftover_len > 0) {
2684             for (i=0; i<leftover_len; i++)
2685               buf[i] = buf[leftover_start + i];
2686         }
2687
2688     /* copy new characters into the buffer */
2689     bp = buf + leftover_len;
2690     buf_len=leftover_len;
2691     for (i=0; i<count; i++)
2692     {
2693         // ignore these
2694         if (data[i] == '\r')
2695             continue;
2696
2697         // join lines split by ICS?
2698         if (!appData.noJoin)
2699         {
2700             /*
2701                 Joining just consists of finding matches against the
2702                 continuation sequence, and discarding that sequence
2703                 if found instead of copying it.  So, until a match
2704                 fails, there's nothing to do since it might be the
2705                 complete sequence, and thus, something we don't want
2706                 copied.
2707             */
2708             if (data[i] == cont_seq[cmatch])
2709             {
2710                 cmatch++;
2711                 if (cmatch == strlen(cont_seq))
2712                 {
2713                     cmatch = 0; // complete match.  just reset the counter
2714
2715                     /*
2716                         it's possible for the ICS to not include the space
2717                         at the end of the last word, making our [correct]
2718                         join operation fuse two separate words.  the server
2719                         does this when the space occurs at the width setting.
2720                     */
2721                     if (!buf_len || buf[buf_len-1] != ' ')
2722                     {
2723                         *bp++ = ' ';
2724                         buf_len++;
2725                     }
2726                 }
2727                 continue;
2728             }
2729             else if (cmatch)
2730             {
2731                 /*
2732                     match failed, so we have to copy what matched before
2733                     falling through and copying this character.  In reality,
2734                     this will only ever be just the newline character, but
2735                     it doesn't hurt to be precise.
2736                 */
2737                 strncpy(bp, cont_seq, cmatch);
2738                 bp += cmatch;
2739                 buf_len += cmatch;
2740                 cmatch = 0;
2741             }
2742         }
2743
2744         // copy this char
2745         *bp++ = data[i];
2746         buf_len++;
2747     }
2748
2749         buf[buf_len] = NULLCHAR;
2750 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2751         next_out = 0;
2752         leftover_start = 0;
2753
2754         i = 0;
2755         while (i < buf_len) {
2756             /* Deal with part of the TELNET option negotiation
2757                protocol.  We refuse to do anything beyond the
2758                defaults, except that we allow the WILL ECHO option,
2759                which ICS uses to turn off password echoing when we are
2760                directly connected to it.  We reject this option
2761                if localLineEditing mode is on (always on in xboard)
2762                and we are talking to port 23, which might be a real
2763                telnet server that will try to keep WILL ECHO on permanently.
2764              */
2765             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2766                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2767                 unsigned char option;
2768                 oldi = i;
2769                 switch ((unsigned char) buf[++i]) {
2770                   case TN_WILL:
2771                     if (appData.debugMode)
2772                       fprintf(debugFP, "\n<WILL ");
2773                     switch (option = (unsigned char) buf[++i]) {
2774                       case TN_ECHO:
2775                         if (appData.debugMode)
2776                           fprintf(debugFP, "ECHO ");
2777                         /* Reply only if this is a change, according
2778                            to the protocol rules. */
2779                         if (remoteEchoOption) break;
2780                         if (appData.localLineEditing &&
2781                             atoi(appData.icsPort) == TN_PORT) {
2782                             TelnetRequest(TN_DONT, TN_ECHO);
2783                         } else {
2784                             EchoOff();
2785                             TelnetRequest(TN_DO, TN_ECHO);
2786                             remoteEchoOption = TRUE;
2787                         }
2788                         break;
2789                       default:
2790                         if (appData.debugMode)
2791                           fprintf(debugFP, "%d ", option);
2792                         /* Whatever this is, we don't want it. */
2793                         TelnetRequest(TN_DONT, option);
2794                         break;
2795                     }
2796                     break;
2797                   case TN_WONT:
2798                     if (appData.debugMode)
2799                       fprintf(debugFP, "\n<WONT ");
2800                     switch (option = (unsigned char) buf[++i]) {
2801                       case TN_ECHO:
2802                         if (appData.debugMode)
2803                           fprintf(debugFP, "ECHO ");
2804                         /* Reply only if this is a change, according
2805                            to the protocol rules. */
2806                         if (!remoteEchoOption) break;
2807                         EchoOn();
2808                         TelnetRequest(TN_DONT, TN_ECHO);
2809                         remoteEchoOption = FALSE;
2810                         break;
2811                       default:
2812                         if (appData.debugMode)
2813                           fprintf(debugFP, "%d ", (unsigned char) option);
2814                         /* Whatever this is, it must already be turned
2815                            off, because we never agree to turn on
2816                            anything non-default, so according to the
2817                            protocol rules, we don't reply. */
2818                         break;
2819                     }
2820                     break;
2821                   case TN_DO:
2822                     if (appData.debugMode)
2823                       fprintf(debugFP, "\n<DO ");
2824                     switch (option = (unsigned char) buf[++i]) {
2825                       default:
2826                         /* Whatever this is, we refuse to do it. */
2827                         if (appData.debugMode)
2828                           fprintf(debugFP, "%d ", option);
2829                         TelnetRequest(TN_WONT, option);
2830                         break;
2831                     }
2832                     break;
2833                   case TN_DONT:
2834                     if (appData.debugMode)
2835                       fprintf(debugFP, "\n<DONT ");
2836                     switch (option = (unsigned char) buf[++i]) {
2837                       default:
2838                         if (appData.debugMode)
2839                           fprintf(debugFP, "%d ", option);
2840                         /* Whatever this is, we are already not doing
2841                            it, because we never agree to do anything
2842                            non-default, so according to the protocol
2843                            rules, we don't reply. */
2844                         break;
2845                     }
2846                     break;
2847                   case TN_IAC:
2848                     if (appData.debugMode)
2849                       fprintf(debugFP, "\n<IAC ");
2850                     /* Doubled IAC; pass it through */
2851                     i--;
2852                     break;
2853                   default:
2854                     if (appData.debugMode)
2855                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2856                     /* Drop all other telnet commands on the floor */
2857                     break;
2858                 }
2859                 if (oldi > next_out)
2860                   SendToPlayer(&buf[next_out], oldi - next_out);
2861                 if (++i > next_out)
2862                   next_out = i;
2863                 continue;
2864             }
2865
2866             /* OK, this at least will *usually* work */
2867             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2868                 loggedOn = TRUE;
2869             }
2870
2871             if (loggedOn && !intfSet) {
2872                 if (ics_type == ICS_ICC) {
2873                   snprintf(str, MSG_SIZ,
2874                           "/set-quietly interface %s\n/set-quietly style 12\n",
2875                           programVersion);
2876                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2877                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2878                 } else if (ics_type == ICS_CHESSNET) {
2879                   snprintf(str, MSG_SIZ, "/style 12\n");
2880                 } else {
2881                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2882                   strcat(str, programVersion);
2883                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2884                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2885                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2886 #ifdef WIN32
2887                   strcat(str, "$iset nohighlight 1\n");
2888 #endif
2889                   strcat(str, "$iset lock 1\n$style 12\n");
2890                 }
2891                 SendToICS(str);
2892                 NotifyFrontendLogin();
2893                 intfSet = TRUE;
2894             }
2895
2896             if (started == STARTED_COMMENT) {
2897                 /* Accumulate characters in comment */
2898                 parse[parse_pos++] = buf[i];
2899                 if (buf[i] == '\n') {
2900                     parse[parse_pos] = NULLCHAR;
2901                     if(chattingPartner>=0) {
2902                         char mess[MSG_SIZ];
2903                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2904                         OutputChatMessage(chattingPartner, mess);
2905                         chattingPartner = -1;
2906                         next_out = i+1; // [HGM] suppress printing in ICS window
2907                     } else
2908                     if(!suppressKibitz) // [HGM] kibitz
2909                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2910                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2911                         int nrDigit = 0, nrAlph = 0, j;
2912                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2913                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2914                         parse[parse_pos] = NULLCHAR;
2915                         // try to be smart: if it does not look like search info, it should go to
2916                         // ICS interaction window after all, not to engine-output window.
2917                         for(j=0; j<parse_pos; j++) { // count letters and digits
2918                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2919                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2920                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2921                         }
2922                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2923                             int depth=0; float score;
2924                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2925                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2926                                 pvInfoList[forwardMostMove-1].depth = depth;
2927                                 pvInfoList[forwardMostMove-1].score = 100*score;
2928                             }
2929                             OutputKibitz(suppressKibitz, parse);
2930                         } else {
2931                             char tmp[MSG_SIZ];
2932                             if(gameMode == IcsObserving) // restore original ICS messages
2933                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2934                             else
2935                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2936                             SendToPlayer(tmp, strlen(tmp));
2937                         }
2938                         next_out = i+1; // [HGM] suppress printing in ICS window
2939                     }
2940                     started = STARTED_NONE;
2941                 } else {
2942                     /* Don't match patterns against characters in comment */
2943                     i++;
2944                     continue;
2945                 }
2946             }
2947             if (started == STARTED_CHATTER) {
2948                 if (buf[i] != '\n') {
2949                     /* Don't match patterns against characters in chatter */
2950                     i++;
2951                     continue;
2952                 }
2953                 started = STARTED_NONE;
2954                 if(suppressKibitz) next_out = i+1;
2955             }
2956
2957             /* Kludge to deal with rcmd protocol */
2958             if (firstTime && looking_at(buf, &i, "\001*")) {
2959                 DisplayFatalError(&buf[1], 0, 1);
2960                 continue;
2961             } else {
2962                 firstTime = FALSE;
2963             }
2964
2965             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2966                 ics_type = ICS_ICC;
2967                 ics_prefix = "/";
2968                 if (appData.debugMode)
2969                   fprintf(debugFP, "ics_type %d\n", ics_type);
2970                 continue;
2971             }
2972             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2973                 ics_type = ICS_FICS;
2974                 ics_prefix = "$";
2975                 if (appData.debugMode)
2976                   fprintf(debugFP, "ics_type %d\n", ics_type);
2977                 continue;
2978             }
2979             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2980                 ics_type = ICS_CHESSNET;
2981                 ics_prefix = "/";
2982                 if (appData.debugMode)
2983                   fprintf(debugFP, "ics_type %d\n", ics_type);
2984                 continue;
2985             }
2986
2987             if (!loggedOn &&
2988                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2989                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2990                  looking_at(buf, &i, "will be \"*\""))) {
2991               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2992               continue;
2993             }
2994
2995             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2996               char buf[MSG_SIZ];
2997               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2998               DisplayIcsInteractionTitle(buf);
2999               have_set_title = TRUE;
3000             }
3001
3002             /* skip finger notes */
3003             if (started == STARTED_NONE &&
3004                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3005                  (buf[i] == '1' && buf[i+1] == '0')) &&
3006                 buf[i+2] == ':' && buf[i+3] == ' ') {
3007               started = STARTED_CHATTER;
3008               i += 3;
3009               continue;
3010             }
3011
3012             oldi = i;
3013             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3014             if(appData.seekGraph) {
3015                 if(soughtPending && MatchSoughtLine(buf+i)) {
3016                     i = strstr(buf+i, "rated") - buf;
3017                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3018                     next_out = leftover_start = i;
3019                     started = STARTED_CHATTER;
3020                     suppressKibitz = TRUE;
3021                     continue;
3022                 }
3023                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3024                         && looking_at(buf, &i, "* ads displayed")) {
3025                     soughtPending = FALSE;
3026                     seekGraphUp = TRUE;
3027                     DrawSeekGraph();
3028                     continue;
3029                 }
3030                 if(appData.autoRefresh) {
3031                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3032                         int s = (ics_type == ICS_ICC); // ICC format differs
3033                         if(seekGraphUp)
3034                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3035                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3036                         looking_at(buf, &i, "*% "); // eat prompt
3037                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3038                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3039                         next_out = i; // suppress
3040                         continue;
3041                     }
3042                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3043                         char *p = star_match[0];
3044                         while(*p) {
3045                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3046                             while(*p && *p++ != ' '); // next
3047                         }
3048                         looking_at(buf, &i, "*% "); // eat prompt
3049                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3050                         next_out = i;
3051                         continue;
3052                     }
3053                 }
3054             }
3055
3056             /* skip formula vars */
3057             if (started == STARTED_NONE &&
3058                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3059               started = STARTED_CHATTER;
3060               i += 3;
3061               continue;
3062             }
3063
3064             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3065             if (appData.autoKibitz && started == STARTED_NONE &&
3066                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3067                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3068                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3069                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3070                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3071                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3072                         suppressKibitz = TRUE;
3073                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3074                         next_out = i;
3075                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3076                                 && (gameMode == IcsPlayingWhite)) ||
3077                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3078                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3079                             started = STARTED_CHATTER; // own kibitz we simply discard
3080                         else {
3081                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3082                             parse_pos = 0; parse[0] = NULLCHAR;
3083                             savingComment = TRUE;
3084                             suppressKibitz = gameMode != IcsObserving ? 2 :
3085                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3086                         }
3087                         continue;
3088                 } else
3089                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3090                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3091                          && atoi(star_match[0])) {
3092                     // suppress the acknowledgements of our own autoKibitz
3093                     char *p;
3094                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3095                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3096                     SendToPlayer(star_match[0], strlen(star_match[0]));
3097                     if(looking_at(buf, &i, "*% ")) // eat prompt
3098                         suppressKibitz = FALSE;
3099                     next_out = i;
3100                     continue;
3101                 }
3102             } // [HGM] kibitz: end of patch
3103
3104             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3105
3106             // [HGM] chat: intercept tells by users for which we have an open chat window
3107             channel = -1;
3108             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3109                                            looking_at(buf, &i, "* whispers:") ||
3110                                            looking_at(buf, &i, "* kibitzes:") ||
3111                                            looking_at(buf, &i, "* shouts:") ||
3112                                            looking_at(buf, &i, "* c-shouts:") ||
3113                                            looking_at(buf, &i, "--> * ") ||
3114                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3115                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3116                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3117                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3118                 int p;
3119                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3120                 chattingPartner = -1;
3121
3122                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3123                 for(p=0; p<MAX_CHAT; p++) {
3124                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3125                     talker[0] = '['; strcat(talker, "] ");
3126                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3127                     chattingPartner = p; break;
3128                     }
3129                 } else
3130                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3131                 for(p=0; p<MAX_CHAT; p++) {
3132                     if(!strcmp("kibitzes", chatPartner[p])) {
3133                         talker[0] = '['; strcat(talker, "] ");
3134                         chattingPartner = p; break;
3135                     }
3136                 } else
3137                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3138                 for(p=0; p<MAX_CHAT; p++) {
3139                     if(!strcmp("whispers", chatPartner[p])) {
3140                         talker[0] = '['; strcat(talker, "] ");
3141                         chattingPartner = p; break;
3142                     }
3143                 } else
3144                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3145                   if(buf[i-8] == '-' && buf[i-3] == 't')
3146                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3147                     if(!strcmp("c-shouts", chatPartner[p])) {
3148                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3149                         chattingPartner = p; break;
3150                     }
3151                   }
3152                   if(chattingPartner < 0)
3153                   for(p=0; p<MAX_CHAT; p++) {
3154                     if(!strcmp("shouts", chatPartner[p])) {
3155                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3156                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3157                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3158                         chattingPartner = p; break;
3159                     }
3160                   }
3161                 }
3162                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3163                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3164                     talker[0] = 0; Colorize(ColorTell, FALSE);
3165                     chattingPartner = p; break;
3166                 }
3167                 if(chattingPartner<0) i = oldi; else {
3168                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3169                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3170                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3171                     started = STARTED_COMMENT;
3172                     parse_pos = 0; parse[0] = NULLCHAR;
3173                     savingComment = 3 + chattingPartner; // counts as TRUE
3174                     suppressKibitz = TRUE;
3175                     continue;
3176                 }
3177             } // [HGM] chat: end of patch
3178
3179           backup = i;
3180             if (appData.zippyTalk || appData.zippyPlay) {
3181                 /* [DM] Backup address for color zippy lines */
3182 #if ZIPPY
3183                if (loggedOn == TRUE)
3184                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3185                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3186 #endif
3187             } // [DM] 'else { ' deleted
3188                 if (
3189                     /* Regular tells and says */
3190                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3191                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3192                     looking_at(buf, &i, "* says: ") ||
3193                     /* Don't color "message" or "messages" output */
3194                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3195                     looking_at(buf, &i, "*. * at *:*: ") ||
3196                     looking_at(buf, &i, "--* (*:*): ") ||
3197                     /* Message notifications (same color as tells) */
3198                     looking_at(buf, &i, "* has left a message ") ||
3199                     looking_at(buf, &i, "* just sent you a message:\n") ||
3200                     /* Whispers and kibitzes */
3201                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3202                     looking_at(buf, &i, "* kibitzes: ") ||
3203                     /* Channel tells */
3204                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3205
3206                   if (tkind == 1 && strchr(star_match[0], ':')) {
3207                       /* Avoid "tells you:" spoofs in channels */
3208                      tkind = 3;
3209                   }
3210                   if (star_match[0][0] == NULLCHAR ||
3211                       strchr(star_match[0], ' ') ||
3212                       (tkind == 3 && strchr(star_match[1], ' '))) {
3213                     /* Reject bogus matches */
3214                     i = oldi;
3215                   } else {
3216                     if (appData.colorize) {
3217                       if (oldi > next_out) {
3218                         SendToPlayer(&buf[next_out], oldi - next_out);
3219                         next_out = oldi;
3220                       }
3221                       switch (tkind) {
3222                       case 1:
3223                         Colorize(ColorTell, FALSE);
3224                         curColor = ColorTell;
3225                         break;
3226                       case 2:
3227                         Colorize(ColorKibitz, FALSE);
3228                         curColor = ColorKibitz;
3229                         break;
3230                       case 3:
3231                         p = strrchr(star_match[1], '(');
3232                         if (p == NULL) {
3233                           p = star_match[1];
3234                         } else {
3235                           p++;
3236                         }
3237                         if (atoi(p) == 1) {
3238                           Colorize(ColorChannel1, FALSE);
3239                           curColor = ColorChannel1;
3240                         } else {
3241                           Colorize(ColorChannel, FALSE);
3242                           curColor = ColorChannel;
3243                         }
3244                         break;
3245                       case 5:
3246                         curColor = ColorNormal;
3247                         break;
3248                       }
3249                     }
3250                     if (started == STARTED_NONE && appData.autoComment &&
3251                         (gameMode == IcsObserving ||
3252                          gameMode == IcsPlayingWhite ||
3253                          gameMode == IcsPlayingBlack)) {
3254                       parse_pos = i - oldi;
3255                       memcpy(parse, &buf[oldi], parse_pos);
3256                       parse[parse_pos] = NULLCHAR;
3257                       started = STARTED_COMMENT;
3258                       savingComment = TRUE;
3259                     } else {
3260                       started = STARTED_CHATTER;
3261                       savingComment = FALSE;
3262                     }
3263                     loggedOn = TRUE;
3264                     continue;
3265                   }
3266                 }
3267
3268                 if (looking_at(buf, &i, "* s-shouts: ") ||
3269                     looking_at(buf, &i, "* c-shouts: ")) {
3270                     if (appData.colorize) {
3271                         if (oldi > next_out) {
3272                             SendToPlayer(&buf[next_out], oldi - next_out);
3273                             next_out = oldi;
3274                         }
3275                         Colorize(ColorSShout, FALSE);
3276                         curColor = ColorSShout;
3277                     }
3278                     loggedOn = TRUE;
3279                     started = STARTED_CHATTER;
3280                     continue;
3281                 }
3282
3283                 if (looking_at(buf, &i, "--->")) {
3284                     loggedOn = TRUE;
3285                     continue;
3286                 }
3287
3288                 if (looking_at(buf, &i, "* shouts: ") ||
3289                     looking_at(buf, &i, "--> ")) {
3290                     if (appData.colorize) {
3291                         if (oldi > next_out) {
3292                             SendToPlayer(&buf[next_out], oldi - next_out);
3293                             next_out = oldi;
3294                         }
3295                         Colorize(ColorShout, FALSE);
3296                         curColor = ColorShout;
3297                     }
3298                     loggedOn = TRUE;
3299                     started = STARTED_CHATTER;
3300                     continue;
3301                 }
3302
3303                 if (looking_at( buf, &i, "Challenge:")) {
3304                     if (appData.colorize) {
3305                         if (oldi > next_out) {
3306                             SendToPlayer(&buf[next_out], oldi - next_out);
3307                             next_out = oldi;
3308                         }
3309                         Colorize(ColorChallenge, FALSE);
3310                         curColor = ColorChallenge;
3311                     }
3312                     loggedOn = TRUE;
3313                     continue;
3314                 }
3315
3316                 if (looking_at(buf, &i, "* offers you") ||
3317                     looking_at(buf, &i, "* offers to be") ||
3318                     looking_at(buf, &i, "* would like to") ||
3319                     looking_at(buf, &i, "* requests to") ||
3320                     looking_at(buf, &i, "Your opponent offers") ||
3321                     looking_at(buf, &i, "Your opponent requests")) {
3322
3323                     if (appData.colorize) {
3324                         if (oldi > next_out) {
3325                             SendToPlayer(&buf[next_out], oldi - next_out);
3326                             next_out = oldi;
3327                         }
3328                         Colorize(ColorRequest, FALSE);
3329                         curColor = ColorRequest;
3330                     }
3331                     continue;
3332                 }
3333
3334                 if (looking_at(buf, &i, "* (*) seeking")) {
3335                     if (appData.colorize) {
3336                         if (oldi > next_out) {
3337                             SendToPlayer(&buf[next_out], oldi - next_out);
3338                             next_out = oldi;
3339                         }
3340                         Colorize(ColorSeek, FALSE);
3341                         curColor = ColorSeek;
3342                     }
3343                     continue;
3344             }
3345
3346           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3347
3348             if (looking_at(buf, &i, "\\   ")) {
3349                 if (prevColor != ColorNormal) {
3350                     if (oldi > next_out) {
3351                         SendToPlayer(&buf[next_out], oldi - next_out);
3352                         next_out = oldi;
3353                     }
3354                     Colorize(prevColor, TRUE);
3355                     curColor = prevColor;
3356                 }
3357                 if (savingComment) {
3358                     parse_pos = i - oldi;
3359                     memcpy(parse, &buf[oldi], parse_pos);
3360                     parse[parse_pos] = NULLCHAR;
3361                     started = STARTED_COMMENT;
3362                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3363                         chattingPartner = savingComment - 3; // kludge to remember the box
3364                 } else {
3365                     started = STARTED_CHATTER;
3366                 }
3367                 continue;
3368             }
3369
3370             if (looking_at(buf, &i, "Black Strength :") ||
3371                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3372                 looking_at(buf, &i, "<10>") ||
3373                 looking_at(buf, &i, "#@#")) {
3374                 /* Wrong board style */
3375                 loggedOn = TRUE;
3376                 SendToICS(ics_prefix);
3377                 SendToICS("set style 12\n");
3378                 SendToICS(ics_prefix);
3379                 SendToICS("refresh\n");
3380                 continue;
3381             }
3382
3383             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3384                 ICSInitScript();
3385                 have_sent_ICS_logon = 1;
3386                 continue;
3387             }
3388
3389             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3390                 (looking_at(buf, &i, "\n<12> ") ||
3391                  looking_at(buf, &i, "<12> "))) {
3392                 loggedOn = TRUE;
3393                 if (oldi > next_out) {
3394                     SendToPlayer(&buf[next_out], oldi - next_out);
3395                 }
3396                 next_out = i;
3397                 started = STARTED_BOARD;
3398                 parse_pos = 0;
3399                 continue;
3400             }
3401
3402             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3403                 looking_at(buf, &i, "<b1> ")) {
3404                 if (oldi > next_out) {
3405                     SendToPlayer(&buf[next_out], oldi - next_out);
3406                 }
3407                 next_out = i;
3408                 started = STARTED_HOLDINGS;
3409                 parse_pos = 0;
3410                 continue;
3411             }
3412
3413             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3414                 loggedOn = TRUE;
3415                 /* Header for a move list -- first line */
3416
3417                 switch (ics_getting_history) {
3418                   case H_FALSE:
3419                     switch (gameMode) {
3420                       case IcsIdle:
3421                       case BeginningOfGame:
3422                         /* User typed "moves" or "oldmoves" while we
3423                            were idle.  Pretend we asked for these
3424                            moves and soak them up so user can step
3425                            through them and/or save them.
3426                            */
3427                         Reset(FALSE, TRUE);
3428                         gameMode = IcsObserving;
3429                         ModeHighlight();
3430                         ics_gamenum = -1;
3431                         ics_getting_history = H_GOT_UNREQ_HEADER;
3432                         break;
3433                       case EditGame: /*?*/
3434                       case EditPosition: /*?*/
3435                         /* Should above feature work in these modes too? */
3436                         /* For now it doesn't */
3437                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3438                         break;
3439                       default:
3440                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3441                         break;
3442                     }
3443                     break;
3444                   case H_REQUESTED:
3445                     /* Is this the right one? */
3446                     if (gameInfo.white && gameInfo.black &&
3447                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3448                         strcmp(gameInfo.black, star_match[2]) == 0) {
3449                         /* All is well */
3450                         ics_getting_history = H_GOT_REQ_HEADER;
3451                     }
3452                     break;
3453                   case H_GOT_REQ_HEADER:
3454                   case H_GOT_UNREQ_HEADER:
3455                   case H_GOT_UNWANTED_HEADER:
3456                   case H_GETTING_MOVES:
3457                     /* Should not happen */
3458                     DisplayError(_("Error gathering move list: two headers"), 0);
3459                     ics_getting_history = H_FALSE;
3460                     break;
3461                 }
3462
3463                 /* Save player ratings into gameInfo if needed */
3464                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3465                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3466                     (gameInfo.whiteRating == -1 ||
3467                      gameInfo.blackRating == -1)) {
3468
3469                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3470                     gameInfo.blackRating = string_to_rating(star_match[3]);
3471                     if (appData.debugMode)
3472                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3473                               gameInfo.whiteRating, gameInfo.blackRating);
3474                 }
3475                 continue;
3476             }
3477
3478             if (looking_at(buf, &i,
3479               "* * match, initial time: * minute*, increment: * second")) {
3480                 /* Header for a move list -- second line */
3481                 /* Initial board will follow if this is a wild game */
3482                 if (gameInfo.event != NULL) free(gameInfo.event);
3483                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3484                 gameInfo.event = StrSave(str);
3485                 /* [HGM] we switched variant. Translate boards if needed. */
3486                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3487                 continue;
3488             }
3489
3490             if (looking_at(buf, &i, "Move  ")) {
3491                 /* Beginning of a move list */
3492                 switch (ics_getting_history) {
3493                   case H_FALSE:
3494                     /* Normally should not happen */
3495                     /* Maybe user hit reset while we were parsing */
3496                     break;
3497                   case H_REQUESTED:
3498                     /* Happens if we are ignoring a move list that is not
3499                      * the one we just requested.  Common if the user
3500                      * tries to observe two games without turning off
3501                      * getMoveList */
3502                     break;
3503                   case H_GETTING_MOVES:
3504                     /* Should not happen */
3505                     DisplayError(_("Error gathering move list: nested"), 0);
3506                     ics_getting_history = H_FALSE;
3507                     break;
3508                   case H_GOT_REQ_HEADER:
3509                     ics_getting_history = H_GETTING_MOVES;
3510                     started = STARTED_MOVES;
3511                     parse_pos = 0;
3512                     if (oldi > next_out) {
3513                         SendToPlayer(&buf[next_out], oldi - next_out);
3514                     }
3515                     break;
3516                   case H_GOT_UNREQ_HEADER:
3517                     ics_getting_history = H_GETTING_MOVES;
3518                     started = STARTED_MOVES_NOHIDE;
3519                     parse_pos = 0;
3520                     break;
3521                   case H_GOT_UNWANTED_HEADER:
3522                     ics_getting_history = H_FALSE;
3523                     break;
3524                 }
3525                 continue;
3526             }
3527
3528             if (looking_at(buf, &i, "% ") ||
3529                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3530                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3531                 if(soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3532                     soughtPending = FALSE;
3533                     seekGraphUp = TRUE;
3534                     DrawSeekGraph();
3535                 }
3536                 if(suppressKibitz) next_out = i;
3537                 savingComment = FALSE;
3538                 suppressKibitz = 0;
3539                 switch (started) {
3540                   case STARTED_MOVES:
3541                   case STARTED_MOVES_NOHIDE:
3542                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3543                     parse[parse_pos + i - oldi] = NULLCHAR;
3544                     ParseGameHistory(parse);
3545 #if ZIPPY
3546                     if (appData.zippyPlay && first.initDone) {
3547                         FeedMovesToProgram(&first, forwardMostMove);
3548                         if (gameMode == IcsPlayingWhite) {
3549                             if (WhiteOnMove(forwardMostMove)) {
3550                                 if (first.sendTime) {
3551                                   if (first.useColors) {
3552                                     SendToProgram("black\n", &first);
3553                                   }
3554                                   SendTimeRemaining(&first, TRUE);
3555                                 }
3556                                 if (first.useColors) {
3557                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3558                                 }
3559                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3560                                 first.maybeThinking = TRUE;
3561                             } else {
3562                                 if (first.usePlayother) {
3563                                   if (first.sendTime) {
3564                                     SendTimeRemaining(&first, TRUE);
3565                                   }
3566                                   SendToProgram("playother\n", &first);
3567                                   firstMove = FALSE;
3568                                 } else {
3569                                   firstMove = TRUE;
3570                                 }
3571                             }
3572                         } else if (gameMode == IcsPlayingBlack) {
3573                             if (!WhiteOnMove(forwardMostMove)) {
3574                                 if (first.sendTime) {
3575                                   if (first.useColors) {
3576                                     SendToProgram("white\n", &first);
3577                                   }
3578                                   SendTimeRemaining(&first, FALSE);
3579                                 }
3580                                 if (first.useColors) {
3581                                   SendToProgram("black\n", &first);
3582                                 }
3583                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3584                                 first.maybeThinking = TRUE;
3585                             } else {
3586                                 if (first.usePlayother) {
3587                                   if (first.sendTime) {
3588                                     SendTimeRemaining(&first, FALSE);
3589                                   }
3590                                   SendToProgram("playother\n", &first);
3591                                   firstMove = FALSE;
3592                                 } else {
3593                                   firstMove = TRUE;
3594                                 }
3595                             }
3596                         }
3597                     }
3598 #endif
3599                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3600                         /* Moves came from oldmoves or moves command
3601                            while we weren't doing anything else.
3602                            */
3603                         currentMove = forwardMostMove;
3604                         ClearHighlights();/*!!could figure this out*/
3605                         flipView = appData.flipView;
3606                         DrawPosition(TRUE, boards[currentMove]);
3607                         DisplayBothClocks();
3608                         snprintf(str, MSG_SIZ, "%s %s %s",
3609                                 gameInfo.white, _("vs."),  gameInfo.black);
3610                         DisplayTitle(str);
3611                         gameMode = IcsIdle;
3612                     } else {
3613                         /* Moves were history of an active game */
3614                         if (gameInfo.resultDetails != NULL) {
3615                             free(gameInfo.resultDetails);
3616                             gameInfo.resultDetails = NULL;
3617                         }
3618                     }
3619                     HistorySet(parseList, backwardMostMove,
3620                                forwardMostMove, currentMove-1);
3621                     DisplayMove(currentMove - 1);
3622                     if (started == STARTED_MOVES) next_out = i;
3623                     started = STARTED_NONE;
3624                     ics_getting_history = H_FALSE;
3625                     break;
3626
3627                   case STARTED_OBSERVE:
3628                     started = STARTED_NONE;
3629                     SendToICS(ics_prefix);
3630                     SendToICS("refresh\n");
3631                     break;
3632
3633                   default:
3634                     break;
3635                 }
3636                 if(bookHit) { // [HGM] book: simulate book reply
3637                     static char bookMove[MSG_SIZ]; // a bit generous?
3638
3639                     programStats.nodes = programStats.depth = programStats.time =
3640                     programStats.score = programStats.got_only_move = 0;
3641                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3642
3643                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3644                     strcat(bookMove, bookHit);
3645                     HandleMachineMove(bookMove, &first);
3646                 }
3647                 continue;
3648             }
3649
3650             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3651                  started == STARTED_HOLDINGS ||
3652                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3653                 /* Accumulate characters in move list or board */
3654                 parse[parse_pos++] = buf[i];
3655             }
3656
3657             /* Start of game messages.  Mostly we detect start of game
3658                when the first board image arrives.  On some versions
3659                of the ICS, though, we need to do a "refresh" after starting
3660                to observe in order to get the current board right away. */
3661             if (looking_at(buf, &i, "Adding game * to observation list")) {
3662                 started = STARTED_OBSERVE;
3663                 continue;
3664             }
3665
3666             /* Handle auto-observe */
3667             if (appData.autoObserve &&
3668                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3669                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3670                 char *player;
3671                 /* Choose the player that was highlighted, if any. */
3672                 if (star_match[0][0] == '\033' ||
3673                     star_match[1][0] != '\033') {
3674                     player = star_match[0];
3675                 } else {
3676                     player = star_match[2];
3677                 }
3678                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3679                         ics_prefix, StripHighlightAndTitle(player));
3680                 SendToICS(str);
3681
3682                 /* Save ratings from notify string */
3683                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3684                 player1Rating = string_to_rating(star_match[1]);
3685                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3686                 player2Rating = string_to_rating(star_match[3]);
3687
3688                 if (appData.debugMode)
3689                   fprintf(debugFP,
3690                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3691                           player1Name, player1Rating,
3692                           player2Name, player2Rating);
3693
3694                 continue;
3695             }
3696
3697             /* Deal with automatic examine mode after a game,
3698                and with IcsObserving -> IcsExamining transition */
3699             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3700                 looking_at(buf, &i, "has made you an examiner of game *")) {
3701
3702                 int gamenum = atoi(star_match[0]);
3703                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3704                     gamenum == ics_gamenum) {
3705                     /* We were already playing or observing this game;
3706                        no need to refetch history */
3707                     gameMode = IcsExamining;
3708                     if (pausing) {
3709                         pauseExamForwardMostMove = forwardMostMove;
3710                     } else if (currentMove < forwardMostMove) {
3711                         ForwardInner(forwardMostMove);
3712                     }
3713                 } else {
3714                     /* I don't think this case really can happen */
3715                     SendToICS(ics_prefix);
3716                     SendToICS("refresh\n");
3717                 }
3718                 continue;
3719             }
3720
3721             /* Error messages */
3722 //          if (ics_user_moved) {
3723             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3724                 if (looking_at(buf, &i, "Illegal move") ||
3725                     looking_at(buf, &i, "Not a legal move") ||
3726                     looking_at(buf, &i, "Your king is in check") ||
3727                     looking_at(buf, &i, "It isn't your turn") ||
3728                     looking_at(buf, &i, "It is not your move")) {
3729                     /* Illegal move */
3730                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3731                         currentMove = forwardMostMove-1;
3732                         DisplayMove(currentMove - 1); /* before DMError */
3733                         DrawPosition(FALSE, boards[currentMove]);
3734                         SwitchClocks(forwardMostMove-1); // [HGM] race
3735                         DisplayBothClocks();
3736                     }
3737                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3738                     ics_user_moved = 0;
3739                     continue;
3740                 }
3741             }
3742
3743             if (looking_at(buf, &i, "still have time") ||
3744                 looking_at(buf, &i, "not out of time") ||
3745                 looking_at(buf, &i, "either player is out of time") ||
3746                 looking_at(buf, &i, "has timeseal; checking")) {
3747                 /* We must have called his flag a little too soon */
3748                 whiteFlag = blackFlag = FALSE;
3749                 continue;
3750             }
3751
3752             if (looking_at(buf, &i, "added * seconds to") ||
3753                 looking_at(buf, &i, "seconds were added to")) {
3754                 /* Update the clocks */
3755                 SendToICS(ics_prefix);
3756                 SendToICS("refresh\n");
3757                 continue;
3758             }
3759
3760             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3761                 ics_clock_paused = TRUE;
3762                 StopClocks();
3763                 continue;
3764             }
3765
3766             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3767                 ics_clock_paused = FALSE;
3768                 StartClocks();
3769                 continue;
3770             }
3771
3772             /* Grab player ratings from the Creating: message.
3773                Note we have to check for the special case when
3774                the ICS inserts things like [white] or [black]. */
3775             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3776                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3777                 /* star_matches:
3778                    0    player 1 name (not necessarily white)
3779                    1    player 1 rating
3780                    2    empty, white, or black (IGNORED)
3781                    3    player 2 name (not necessarily black)
3782                    4    player 2 rating
3783
3784                    The names/ratings are sorted out when the game
3785                    actually starts (below).
3786                 */
3787                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3788                 player1Rating = string_to_rating(star_match[1]);
3789                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3790                 player2Rating = string_to_rating(star_match[4]);
3791
3792                 if (appData.debugMode)
3793                   fprintf(debugFP,
3794                           "Ratings from 'Creating:' %s %d, %s %d\n",
3795                           player1Name, player1Rating,
3796                           player2Name, player2Rating);
3797
3798                 continue;
3799             }
3800
3801             /* Improved generic start/end-of-game messages */
3802             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3803                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3804                 /* If tkind == 0: */
3805                 /* star_match[0] is the game number */
3806                 /*           [1] is the white player's name */
3807                 /*           [2] is the black player's name */
3808                 /* For end-of-game: */
3809                 /*           [3] is the reason for the game end */
3810                 /*           [4] is a PGN end game-token, preceded by " " */
3811                 /* For start-of-game: */
3812                 /*           [3] begins with "Creating" or "Continuing" */
3813                 /*           [4] is " *" or empty (don't care). */
3814                 int gamenum = atoi(star_match[0]);
3815                 char *whitename, *blackname, *why, *endtoken;
3816                 ChessMove endtype = EndOfFile;
3817
3818                 if (tkind == 0) {
3819                   whitename = star_match[1];
3820                   blackname = star_match[2];
3821                   why = star_match[3];
3822                   endtoken = star_match[4];
3823                 } else {
3824                   whitename = star_match[1];
3825                   blackname = star_match[3];
3826                   why = star_match[5];
3827                   endtoken = star_match[6];
3828                 }
3829
3830                 /* Game start messages */
3831                 if (strncmp(why, "Creating ", 9) == 0 ||
3832                     strncmp(why, "Continuing ", 11) == 0) {
3833                     gs_gamenum = gamenum;
3834                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3835                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3836                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3837 #if ZIPPY
3838                     if (appData.zippyPlay) {
3839                         ZippyGameStart(whitename, blackname);
3840                     }
3841 #endif /*ZIPPY*/
3842                     partnerBoardValid = FALSE; // [HGM] bughouse
3843                     continue;
3844                 }
3845
3846                 /* Game end messages */
3847                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3848                     ics_gamenum != gamenum) {
3849                     continue;
3850                 }
3851                 while (endtoken[0] == ' ') endtoken++;
3852                 switch (endtoken[0]) {
3853                   case '*':
3854                   default:
3855                     endtype = GameUnfinished;
3856                     break;
3857                   case '0':
3858                     endtype = BlackWins;
3859                     break;
3860                   case '1':
3861                     if (endtoken[1] == '/')
3862                       endtype = GameIsDrawn;
3863                     else
3864                       endtype = WhiteWins;
3865                     break;
3866                 }
3867                 GameEnds(endtype, why, GE_ICS);
3868 #if ZIPPY
3869                 if (appData.zippyPlay && first.initDone) {
3870                     ZippyGameEnd(endtype, why);
3871                     if (first.pr == NoProc) {
3872                       /* Start the next process early so that we'll
3873                          be ready for the next challenge */
3874                       StartChessProgram(&first);
3875                     }
3876                     /* Send "new" early, in case this command takes
3877                        a long time to finish, so that we'll be ready
3878                        for the next challenge. */
3879                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3880                     Reset(TRUE, TRUE);
3881                 }
3882 #endif /*ZIPPY*/
3883                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3884                 continue;
3885             }
3886
3887             if (looking_at(buf, &i, "Removing game * from observation") ||
3888                 looking_at(buf, &i, "no longer observing game *") ||
3889                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3890                 if (gameMode == IcsObserving &&
3891                     atoi(star_match[0]) == ics_gamenum)
3892                   {
3893                       /* icsEngineAnalyze */
3894                       if (appData.icsEngineAnalyze) {
3895                             ExitAnalyzeMode();
3896                             ModeHighlight();
3897                       }
3898                       StopClocks();
3899                       gameMode = IcsIdle;
3900                       ics_gamenum = -1;
3901                       ics_user_moved = FALSE;
3902                   }
3903                 continue;
3904             }
3905
3906             if (looking_at(buf, &i, "no longer examining game *")) {
3907                 if (gameMode == IcsExamining &&
3908                     atoi(star_match[0]) == ics_gamenum)
3909                   {
3910                       gameMode = IcsIdle;
3911                       ics_gamenum = -1;
3912                       ics_user_moved = FALSE;
3913                   }
3914                 continue;
3915             }
3916
3917             /* Advance leftover_start past any newlines we find,
3918                so only partial lines can get reparsed */
3919             if (looking_at(buf, &i, "\n")) {
3920                 prevColor = curColor;
3921                 if (curColor != ColorNormal) {
3922                     if (oldi > next_out) {
3923                         SendToPlayer(&buf[next_out], oldi - next_out);
3924                         next_out = oldi;
3925                     }
3926                     Colorize(ColorNormal, FALSE);
3927                     curColor = ColorNormal;
3928                 }
3929                 if (started == STARTED_BOARD) {
3930                     started = STARTED_NONE;
3931                     parse[parse_pos] = NULLCHAR;
3932                     ParseBoard12(parse);
3933                     ics_user_moved = 0;
3934
3935                     /* Send premove here */
3936                     if (appData.premove) {
3937                       char str[MSG_SIZ];
3938                       if (currentMove == 0 &&
3939                           gameMode == IcsPlayingWhite &&
3940                           appData.premoveWhite) {
3941                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3942                         if (appData.debugMode)
3943                           fprintf(debugFP, "Sending premove:\n");
3944                         SendToICS(str);
3945                       } else if (currentMove == 1 &&
3946                                  gameMode == IcsPlayingBlack &&
3947                                  appData.premoveBlack) {
3948                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3949                         if (appData.debugMode)
3950                           fprintf(debugFP, "Sending premove:\n");
3951                         SendToICS(str);
3952                       } else if (gotPremove) {
3953                         gotPremove = 0;
3954                         ClearPremoveHighlights();
3955                         if (appData.debugMode)
3956                           fprintf(debugFP, "Sending premove:\n");
3957                           UserMoveEvent(premoveFromX, premoveFromY,
3958                                         premoveToX, premoveToY,
3959                                         premovePromoChar);
3960                       }
3961                     }
3962
3963                     /* Usually suppress following prompt */
3964                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3965                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3966                         if (looking_at(buf, &i, "*% ")) {
3967                             savingComment = FALSE;
3968                             suppressKibitz = 0;
3969                         }
3970                     }
3971                     next_out = i;
3972                 } else if (started == STARTED_HOLDINGS) {
3973                     int gamenum;
3974                     char new_piece[MSG_SIZ];
3975                     started = STARTED_NONE;
3976                     parse[parse_pos] = NULLCHAR;
3977                     if (appData.debugMode)
3978                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3979                                                         parse, currentMove);
3980                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3981                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3982                         if (gameInfo.variant == VariantNormal) {
3983                           /* [HGM] We seem to switch variant during a game!
3984                            * Presumably no holdings were displayed, so we have
3985                            * to move the position two files to the right to
3986                            * create room for them!
3987                            */
3988                           VariantClass newVariant;
3989                           switch(gameInfo.boardWidth) { // base guess on board width
3990                                 case 9:  newVariant = VariantShogi; break;
3991                                 case 10: newVariant = VariantGreat; break;
3992                                 default: newVariant = VariantCrazyhouse; break;
3993                           }
3994                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3995                           /* Get a move list just to see the header, which
3996                              will tell us whether this is really bug or zh */
3997                           if (ics_getting_history == H_FALSE) {
3998                             ics_getting_history = H_REQUESTED;
3999                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4000                             SendToICS(str);
4001                           }
4002                         }
4003                         new_piece[0] = NULLCHAR;
4004                         sscanf(parse, "game %d white [%s black [%s <- %s",
4005                                &gamenum, white_holding, black_holding,
4006                                new_piece);
4007                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4008                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4009                         /* [HGM] copy holdings to board holdings area */
4010                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4011                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4012                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4013 #if ZIPPY
4014                         if (appData.zippyPlay && first.initDone) {
4015                             ZippyHoldings(white_holding, black_holding,
4016                                           new_piece);
4017                         }
4018 #endif /*ZIPPY*/
4019                         if (tinyLayout || smallLayout) {
4020                             char wh[16], bh[16];
4021                             PackHolding(wh, white_holding);
4022                             PackHolding(bh, black_holding);
4023                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4024                                     gameInfo.white, gameInfo.black);
4025                         } else {
4026                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4027                                     gameInfo.white, white_holding, _("vs."),
4028                                     gameInfo.black, black_holding);
4029                         }
4030                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4031                         DrawPosition(FALSE, boards[currentMove]);
4032                         DisplayTitle(str);
4033                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4034                         sscanf(parse, "game %d white [%s black [%s <- %s",
4035                                &gamenum, white_holding, black_holding,
4036                                new_piece);
4037                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4038                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4039                         /* [HGM] copy holdings to partner-board holdings area */
4040                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4041                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4042                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4043                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4044                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4045                       }
4046                     }
4047                     /* Suppress following prompt */
4048                     if (looking_at(buf, &i, "*% ")) {
4049                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4050                         savingComment = FALSE;
4051                         suppressKibitz = 0;
4052                     }
4053                     next_out = i;
4054                 }
4055                 continue;
4056             }
4057
4058             i++;                /* skip unparsed character and loop back */
4059         }
4060
4061         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4062 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4063 //          SendToPlayer(&buf[next_out], i - next_out);
4064             started != STARTED_HOLDINGS && leftover_start > next_out) {
4065             SendToPlayer(&buf[next_out], leftover_start - next_out);
4066             next_out = i;
4067         }
4068
4069         leftover_len = buf_len - leftover_start;
4070         /* if buffer ends with something we couldn't parse,
4071            reparse it after appending the next read */
4072
4073     } else if (count == 0) {
4074         RemoveInputSource(isr);
4075         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4076     } else {
4077         DisplayFatalError(_("Error reading from ICS"), error, 1);
4078     }
4079 }
4080
4081
4082 /* Board style 12 looks like this:
4083
4084    <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
4085
4086  * The "<12> " is stripped before it gets to this routine.  The two
4087  * trailing 0's (flip state and clock ticking) are later addition, and
4088  * some chess servers may not have them, or may have only the first.
4089  * Additional trailing fields may be added in the future.
4090  */
4091
4092 #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"
4093
4094 #define RELATION_OBSERVING_PLAYED    0
4095 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4096 #define RELATION_PLAYING_MYMOVE      1
4097 #define RELATION_PLAYING_NOTMYMOVE  -1
4098 #define RELATION_EXAMINING           2
4099 #define RELATION_ISOLATED_BOARD     -3
4100 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4101
4102 void
4103 ParseBoard12 (char *string)
4104 {
4105     GameMode newGameMode;
4106     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4107     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4108     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4109     char to_play, board_chars[200];
4110     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4111     char black[32], white[32];
4112     Board board;
4113     int prevMove = currentMove;
4114     int ticking = 2;
4115     ChessMove moveType;
4116     int fromX, fromY, toX, toY;
4117     char promoChar;
4118     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4119     char *bookHit = NULL; // [HGM] book
4120     Boolean weird = FALSE, reqFlag = FALSE;
4121
4122     fromX = fromY = toX = toY = -1;
4123
4124     newGame = FALSE;
4125
4126     if (appData.debugMode)
4127       fprintf(debugFP, _("Parsing board: %s\n"), string);
4128
4129     move_str[0] = NULLCHAR;
4130     elapsed_time[0] = NULLCHAR;
4131     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4132         int  i = 0, j;
4133         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4134             if(string[i] == ' ') { ranks++; files = 0; }
4135             else files++;
4136             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4137             i++;
4138         }
4139         for(j = 0; j <i; j++) board_chars[j] = string[j];
4140         board_chars[i] = '\0';
4141         string += i + 1;
4142     }
4143     n = sscanf(string, PATTERN, &to_play, &double_push,
4144                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4145                &gamenum, white, black, &relation, &basetime, &increment,
4146                &white_stren, &black_stren, &white_time, &black_time,
4147                &moveNum, str, elapsed_time, move_str, &ics_flip,
4148                &ticking);
4149
4150     if (n < 21) {
4151         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4152         DisplayError(str, 0);
4153         return;
4154     }
4155
4156     /* Convert the move number to internal form */
4157     moveNum = (moveNum - 1) * 2;
4158     if (to_play == 'B') moveNum++;
4159     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4160       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4161                         0, 1);
4162       return;
4163     }
4164
4165     switch (relation) {
4166       case RELATION_OBSERVING_PLAYED:
4167       case RELATION_OBSERVING_STATIC:
4168         if (gamenum == -1) {
4169             /* Old ICC buglet */
4170             relation = RELATION_OBSERVING_STATIC;
4171         }
4172         newGameMode = IcsObserving;
4173         break;
4174       case RELATION_PLAYING_MYMOVE:
4175       case RELATION_PLAYING_NOTMYMOVE:
4176         newGameMode =
4177           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4178             IcsPlayingWhite : IcsPlayingBlack;
4179         break;
4180       case RELATION_EXAMINING:
4181         newGameMode = IcsExamining;
4182         break;
4183       case RELATION_ISOLATED_BOARD:
4184       default:
4185         /* Just display this board.  If user was doing something else,
4186            we will forget about it until the next board comes. */
4187         newGameMode = IcsIdle;
4188         break;
4189       case RELATION_STARTING_POSITION:
4190         newGameMode = gameMode;
4191         break;
4192     }
4193
4194     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4195          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4196       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4197       char *toSqr;
4198       for (k = 0; k < ranks; k++) {
4199         for (j = 0; j < files; j++)
4200           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4201         if(gameInfo.holdingsWidth > 1) {
4202              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4203              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4204         }
4205       }
4206       CopyBoard(partnerBoard, board);
4207       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4208         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4209         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4210       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4211       if(toSqr = strchr(str, '-')) {
4212         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4213         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4214       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4215       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4216       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4217       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4218       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4219       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4220                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4221       DisplayMessage(partnerStatus, "");
4222         partnerBoardValid = TRUE;
4223       return;
4224     }
4225
4226     /* Modify behavior for initial board display on move listing
4227        of wild games.
4228        */
4229     switch (ics_getting_history) {
4230       case H_FALSE:
4231       case H_REQUESTED:
4232         break;
4233       case H_GOT_REQ_HEADER:
4234       case H_GOT_UNREQ_HEADER:
4235         /* This is the initial position of the current game */
4236         gamenum = ics_gamenum;
4237         moveNum = 0;            /* old ICS bug workaround */
4238         if (to_play == 'B') {
4239           startedFromSetupPosition = TRUE;
4240           blackPlaysFirst = TRUE;
4241           moveNum = 1;
4242           if (forwardMostMove == 0) forwardMostMove = 1;
4243           if (backwardMostMove == 0) backwardMostMove = 1;
4244           if (currentMove == 0) currentMove = 1;
4245         }
4246         newGameMode = gameMode;
4247         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4248         break;
4249       case H_GOT_UNWANTED_HEADER:
4250         /* This is an initial board that we don't want */
4251         return;
4252       case H_GETTING_MOVES:
4253         /* Should not happen */
4254         DisplayError(_("Error gathering move list: extra board"), 0);
4255         ics_getting_history = H_FALSE;
4256         return;
4257     }
4258
4259    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4260                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4261      /* [HGM] We seem to have switched variant unexpectedly
4262       * Try to guess new variant from board size
4263       */
4264           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4265           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4266           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4267           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4268           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4269           if(!weird) newVariant = VariantNormal;
4270           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4271           /* Get a move list just to see the header, which
4272              will tell us whether this is really bug or zh */
4273           if (ics_getting_history == H_FALSE) {
4274             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4275             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4276             SendToICS(str);
4277           }
4278     }
4279
4280     /* Take action if this is the first board of a new game, or of a
4281        different game than is currently being displayed.  */
4282     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4283         relation == RELATION_ISOLATED_BOARD) {
4284
4285         /* Forget the old game and get the history (if any) of the new one */
4286         if (gameMode != BeginningOfGame) {
4287           Reset(TRUE, TRUE);
4288         }
4289         newGame = TRUE;
4290         if (appData.autoRaiseBoard) BoardToTop();
4291         prevMove = -3;
4292         if (gamenum == -1) {
4293             newGameMode = IcsIdle;
4294         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4295                    appData.getMoveList && !reqFlag) {
4296             /* Need to get game history */
4297             ics_getting_history = H_REQUESTED;
4298             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4299             SendToICS(str);
4300         }
4301
4302         /* Initially flip the board to have black on the bottom if playing
4303            black or if the ICS flip flag is set, but let the user change
4304            it with the Flip View button. */
4305         flipView = appData.autoFlipView ?
4306           (newGameMode == IcsPlayingBlack) || ics_flip :
4307           appData.flipView;
4308
4309         /* Done with values from previous mode; copy in new ones */
4310         gameMode = newGameMode;
4311         ModeHighlight();
4312         ics_gamenum = gamenum;
4313         if (gamenum == gs_gamenum) {
4314             int klen = strlen(gs_kind);
4315             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4316             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4317             gameInfo.event = StrSave(str);
4318         } else {
4319             gameInfo.event = StrSave("ICS game");
4320         }
4321         gameInfo.site = StrSave(appData.icsHost);
4322         gameInfo.date = PGNDate();
4323         gameInfo.round = StrSave("-");
4324         gameInfo.white = StrSave(white);
4325         gameInfo.black = StrSave(black);
4326         timeControl = basetime * 60 * 1000;
4327         timeControl_2 = 0;
4328         timeIncrement = increment * 1000;
4329         movesPerSession = 0;
4330         gameInfo.timeControl = TimeControlTagValue();
4331         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4332   if (appData.debugMode) {
4333     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4334     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4335     setbuf(debugFP, NULL);
4336   }
4337
4338         gameInfo.outOfBook = NULL;
4339
4340         /* Do we have the ratings? */
4341         if (strcmp(player1Name, white) == 0 &&
4342             strcmp(player2Name, black) == 0) {
4343             if (appData.debugMode)
4344               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4345                       player1Rating, player2Rating);
4346             gameInfo.whiteRating = player1Rating;
4347             gameInfo.blackRating = player2Rating;
4348         } else if (strcmp(player2Name, white) == 0 &&
4349                    strcmp(player1Name, black) == 0) {
4350             if (appData.debugMode)
4351               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4352                       player2Rating, player1Rating);
4353             gameInfo.whiteRating = player2Rating;
4354             gameInfo.blackRating = player1Rating;
4355         }
4356         player1Name[0] = player2Name[0] = NULLCHAR;
4357
4358         /* Silence shouts if requested */
4359         if (appData.quietPlay &&
4360             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4361             SendToICS(ics_prefix);
4362             SendToICS("set shout 0\n");
4363         }
4364     }
4365
4366     /* Deal with midgame name changes */
4367     if (!newGame) {
4368         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4369             if (gameInfo.white) free(gameInfo.white);
4370             gameInfo.white = StrSave(white);
4371         }
4372         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4373             if (gameInfo.black) free(gameInfo.black);
4374             gameInfo.black = StrSave(black);
4375         }
4376     }
4377
4378     /* Throw away game result if anything actually changes in examine mode */
4379     if (gameMode == IcsExamining && !newGame) {
4380         gameInfo.result = GameUnfinished;
4381         if (gameInfo.resultDetails != NULL) {
4382             free(gameInfo.resultDetails);
4383             gameInfo.resultDetails = NULL;
4384         }
4385     }
4386
4387     /* In pausing && IcsExamining mode, we ignore boards coming
4388        in if they are in a different variation than we are. */
4389     if (pauseExamInvalid) return;
4390     if (pausing && gameMode == IcsExamining) {
4391         if (moveNum <= pauseExamForwardMostMove) {
4392             pauseExamInvalid = TRUE;
4393             forwardMostMove = pauseExamForwardMostMove;
4394             return;
4395         }
4396     }
4397
4398   if (appData.debugMode) {
4399     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4400   }
4401     /* Parse the board */
4402     for (k = 0; k < ranks; k++) {
4403       for (j = 0; j < files; j++)
4404         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4405       if(gameInfo.holdingsWidth > 1) {
4406            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4407            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4408       }
4409     }
4410     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4411       board[5][BOARD_RGHT+1] = WhiteAngel;
4412       board[6][BOARD_RGHT+1] = WhiteMarshall;
4413       board[1][0] = BlackMarshall;
4414       board[2][0] = BlackAngel;
4415       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4416     }
4417     CopyBoard(boards[moveNum], board);
4418     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4419     if (moveNum == 0) {
4420         startedFromSetupPosition =
4421           !CompareBoards(board, initialPosition);
4422         if(startedFromSetupPosition)
4423             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4424     }
4425
4426     /* [HGM] Set castling rights. Take the outermost Rooks,
4427        to make it also work for FRC opening positions. Note that board12
4428        is really defective for later FRC positions, as it has no way to
4429        indicate which Rook can castle if they are on the same side of King.
4430        For the initial position we grant rights to the outermost Rooks,
4431        and remember thos rights, and we then copy them on positions
4432        later in an FRC game. This means WB might not recognize castlings with
4433        Rooks that have moved back to their original position as illegal,
4434        but in ICS mode that is not its job anyway.
4435     */
4436     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4437     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4438
4439         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4440             if(board[0][i] == WhiteRook) j = i;
4441         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4442         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4443             if(board[0][i] == WhiteRook) j = i;
4444         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4445         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4446             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4447         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4448         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4449             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4450         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4451
4452         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4453         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4454         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4455             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4456         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4457             if(board[BOARD_HEIGHT-1][k] == bKing)
4458                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4459         if(gameInfo.variant == VariantTwoKings) {
4460             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4461             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4462             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4463         }
4464     } else { int r;
4465         r = boards[moveNum][CASTLING][0] = initialRights[0];
4466         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4467         r = boards[moveNum][CASTLING][1] = initialRights[1];
4468         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4469         r = boards[moveNum][CASTLING][3] = initialRights[3];
4470         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4471         r = boards[moveNum][CASTLING][4] = initialRights[4];
4472         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4473         /* wildcastle kludge: always assume King has rights */
4474         r = boards[moveNum][CASTLING][2] = initialRights[2];
4475         r = boards[moveNum][CASTLING][5] = initialRights[5];
4476     }
4477     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4478     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4479
4480
4481     if (ics_getting_history == H_GOT_REQ_HEADER ||
4482         ics_getting_history == H_GOT_UNREQ_HEADER) {
4483         /* This was an initial position from a move list, not
4484            the current position */
4485         return;
4486     }
4487
4488     /* Update currentMove and known move number limits */
4489     newMove = newGame || moveNum > forwardMostMove;
4490
4491     if (newGame) {
4492         forwardMostMove = backwardMostMove = currentMove = moveNum;
4493         if (gameMode == IcsExamining && moveNum == 0) {
4494           /* Workaround for ICS limitation: we are not told the wild
4495              type when starting to examine a game.  But if we ask for
4496              the move list, the move list header will tell us */
4497             ics_getting_history = H_REQUESTED;
4498             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4499             SendToICS(str);
4500         }
4501     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4502                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4503 #if ZIPPY
4504         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4505         /* [HGM] applied this also to an engine that is silently watching        */
4506         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4507             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4508             gameInfo.variant == currentlyInitializedVariant) {
4509           takeback = forwardMostMove - moveNum;
4510           for (i = 0; i < takeback; i++) {
4511             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4512             SendToProgram("undo\n", &first);
4513           }
4514         }
4515 #endif
4516
4517         forwardMostMove = moveNum;
4518         if (!pausing || currentMove > forwardMostMove)
4519           currentMove = forwardMostMove;
4520     } else {
4521         /* New part of history that is not contiguous with old part */
4522         if (pausing && gameMode == IcsExamining) {
4523             pauseExamInvalid = TRUE;
4524             forwardMostMove = pauseExamForwardMostMove;
4525             return;
4526         }
4527         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4528 #if ZIPPY
4529             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4530                 // [HGM] when we will receive the move list we now request, it will be
4531                 // fed to the engine from the first move on. So if the engine is not
4532                 // in the initial position now, bring it there.
4533                 InitChessProgram(&first, 0);
4534             }
4535 #endif
4536             ics_getting_history = H_REQUESTED;
4537             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4538             SendToICS(str);
4539         }
4540         forwardMostMove = backwardMostMove = currentMove = moveNum;
4541     }
4542
4543     /* Update the clocks */
4544     if (strchr(elapsed_time, '.')) {
4545       /* Time is in ms */
4546       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4547       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4548     } else {
4549       /* Time is in seconds */
4550       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4551       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4552     }
4553
4554
4555 #if ZIPPY
4556     if (appData.zippyPlay && newGame &&
4557         gameMode != IcsObserving && gameMode != IcsIdle &&
4558         gameMode != IcsExamining)
4559       ZippyFirstBoard(moveNum, basetime, increment);
4560 #endif
4561
4562     /* Put the move on the move list, first converting
4563        to canonical algebraic form. */
4564     if (moveNum > 0) {
4565   if (appData.debugMode) {
4566     if (appData.debugMode) { int f = forwardMostMove;
4567         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4568                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4569                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4570     }
4571     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4572     fprintf(debugFP, "moveNum = %d\n", moveNum);
4573     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4574     setbuf(debugFP, NULL);
4575   }
4576         if (moveNum <= backwardMostMove) {
4577             /* We don't know what the board looked like before
4578                this move.  Punt. */
4579           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4580             strcat(parseList[moveNum - 1], " ");
4581             strcat(parseList[moveNum - 1], elapsed_time);
4582             moveList[moveNum - 1][0] = NULLCHAR;
4583         } else if (strcmp(move_str, "none") == 0) {
4584             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4585             /* Again, we don't know what the board looked like;
4586                this is really the start of the game. */
4587             parseList[moveNum - 1][0] = NULLCHAR;
4588             moveList[moveNum - 1][0] = NULLCHAR;
4589             backwardMostMove = moveNum;
4590             startedFromSetupPosition = TRUE;
4591             fromX = fromY = toX = toY = -1;
4592         } else {
4593           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4594           //                 So we parse the long-algebraic move string in stead of the SAN move
4595           int valid; char buf[MSG_SIZ], *prom;
4596
4597           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4598                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4599           // str looks something like "Q/a1-a2"; kill the slash
4600           if(str[1] == '/')
4601             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4602           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4603           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4604                 strcat(buf, prom); // long move lacks promo specification!
4605           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4606                 if(appData.debugMode)
4607                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4608                 safeStrCpy(move_str, buf, MSG_SIZ);
4609           }
4610           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4611                                 &fromX, &fromY, &toX, &toY, &promoChar)
4612                || ParseOneMove(buf, moveNum - 1, &moveType,
4613                                 &fromX, &fromY, &toX, &toY, &promoChar);
4614           // end of long SAN patch
4615           if (valid) {
4616             (void) CoordsToAlgebraic(boards[moveNum - 1],
4617                                      PosFlags(moveNum - 1),
4618                                      fromY, fromX, toY, toX, promoChar,
4619                                      parseList[moveNum-1]);
4620             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4621               case MT_NONE:
4622               case MT_STALEMATE:
4623               default:
4624                 break;
4625               case MT_CHECK:
4626                 if(gameInfo.variant != VariantShogi)
4627                     strcat(parseList[moveNum - 1], "+");
4628                 break;
4629               case MT_CHECKMATE:
4630               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4631                 strcat(parseList[moveNum - 1], "#");
4632                 break;
4633             }
4634             strcat(parseList[moveNum - 1], " ");
4635             strcat(parseList[moveNum - 1], elapsed_time);
4636             /* currentMoveString is set as a side-effect of ParseOneMove */
4637             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4638             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4639             strcat(moveList[moveNum - 1], "\n");
4640
4641             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4642                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4643               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4644                 ChessSquare old, new = boards[moveNum][k][j];
4645                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4646                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4647                   if(old == new) continue;
4648                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4649                   else if(new == WhiteWazir || new == BlackWazir) {
4650                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4651                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4652                       else boards[moveNum][k][j] = old; // preserve type of Gold
4653                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4654                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4655               }
4656           } else {
4657             /* Move from ICS was illegal!?  Punt. */
4658             if (appData.debugMode) {
4659               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4660               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4661             }
4662             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4663             strcat(parseList[moveNum - 1], " ");
4664             strcat(parseList[moveNum - 1], elapsed_time);
4665             moveList[moveNum - 1][0] = NULLCHAR;
4666             fromX = fromY = toX = toY = -1;
4667           }
4668         }
4669   if (appData.debugMode) {
4670     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4671     setbuf(debugFP, NULL);
4672   }
4673
4674 #if ZIPPY
4675         /* Send move to chess program (BEFORE animating it). */
4676         if (appData.zippyPlay && !newGame && newMove &&
4677            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4678
4679             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4680                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4681                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4682                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4683                             move_str);
4684                     DisplayError(str, 0);
4685                 } else {
4686                     if (first.sendTime) {
4687                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4688                     }
4689                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4690                     if (firstMove && !bookHit) {
4691                         firstMove = FALSE;
4692                         if (first.useColors) {
4693                           SendToProgram(gameMode == IcsPlayingWhite ?
4694                                         "white\ngo\n" :
4695                                         "black\ngo\n", &first);
4696                         } else {
4697                           SendToProgram("go\n", &first);
4698                         }
4699                         first.maybeThinking = TRUE;
4700                     }
4701                 }
4702             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4703               if (moveList[moveNum - 1][0] == NULLCHAR) {
4704                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4705                 DisplayError(str, 0);
4706               } else {
4707                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4708                 SendMoveToProgram(moveNum - 1, &first);
4709               }
4710             }
4711         }
4712 #endif
4713     }
4714
4715     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4716         /* If move comes from a remote source, animate it.  If it
4717            isn't remote, it will have already been animated. */
4718         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4719             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4720         }
4721         if (!pausing && appData.highlightLastMove) {
4722             SetHighlights(fromX, fromY, toX, toY);
4723         }
4724     }
4725
4726     /* Start the clocks */
4727     whiteFlag = blackFlag = FALSE;
4728     appData.clockMode = !(basetime == 0 && increment == 0);
4729     if (ticking == 0) {
4730       ics_clock_paused = TRUE;
4731       StopClocks();
4732     } else if (ticking == 1) {
4733       ics_clock_paused = FALSE;
4734     }
4735     if (gameMode == IcsIdle ||
4736         relation == RELATION_OBSERVING_STATIC ||
4737         relation == RELATION_EXAMINING ||
4738         ics_clock_paused)
4739       DisplayBothClocks();
4740     else
4741       StartClocks();
4742
4743     /* Display opponents and material strengths */
4744     if (gameInfo.variant != VariantBughouse &&
4745         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4746         if (tinyLayout || smallLayout) {
4747             if(gameInfo.variant == VariantNormal)
4748               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4749                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4750                     basetime, increment);
4751             else
4752               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4753                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4754                     basetime, increment, (int) gameInfo.variant);
4755         } else {
4756             if(gameInfo.variant == VariantNormal)
4757               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4758                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4759                     basetime, increment);
4760             else
4761               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4762                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4763                     basetime, increment, VariantName(gameInfo.variant));
4764         }
4765         DisplayTitle(str);
4766   if (appData.debugMode) {
4767     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4768   }
4769     }
4770
4771
4772     /* Display the board */
4773     if (!pausing && !appData.noGUI) {
4774
4775       if (appData.premove)
4776           if (!gotPremove ||
4777              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4778              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4779               ClearPremoveHighlights();
4780
4781       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4782         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4783       DrawPosition(j, boards[currentMove]);
4784
4785       DisplayMove(moveNum - 1);
4786       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4787             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4788               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4789         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4790       }
4791     }
4792
4793     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4794 #if ZIPPY
4795     if(bookHit) { // [HGM] book: simulate book reply
4796         static char bookMove[MSG_SIZ]; // a bit generous?
4797
4798         programStats.nodes = programStats.depth = programStats.time =
4799         programStats.score = programStats.got_only_move = 0;
4800         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4801
4802         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4803         strcat(bookMove, bookHit);
4804         HandleMachineMove(bookMove, &first);
4805     }
4806 #endif
4807 }
4808
4809 void
4810 GetMoveListEvent ()
4811 {
4812     char buf[MSG_SIZ];
4813     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4814         ics_getting_history = H_REQUESTED;
4815         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4816         SendToICS(buf);
4817     }
4818 }
4819
4820 void
4821 AnalysisPeriodicEvent (int force)
4822 {
4823     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4824          && !force) || !appData.periodicUpdates)
4825       return;
4826
4827     /* Send . command to Crafty to collect stats */
4828     SendToProgram(".\n", &first);
4829
4830     /* Don't send another until we get a response (this makes
4831        us stop sending to old Crafty's which don't understand
4832        the "." command (sending illegal cmds resets node count & time,
4833        which looks bad)) */
4834     programStats.ok_to_send = 0;
4835 }
4836
4837 void
4838 ics_update_width (int new_width)
4839 {
4840         ics_printf("set width %d\n", new_width);
4841 }
4842
4843 void
4844 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4845 {
4846     char buf[MSG_SIZ];
4847
4848     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4849         // null move in variant where engine does not understand it (for analysis purposes)
4850         SendBoard(cps, moveNum + 1); // send position after move in stead.
4851         return;
4852     }
4853     if (cps->useUsermove) {
4854       SendToProgram("usermove ", cps);
4855     }
4856     if (cps->useSAN) {
4857       char *space;
4858       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4859         int len = space - parseList[moveNum];
4860         memcpy(buf, parseList[moveNum], len);
4861         buf[len++] = '\n';
4862         buf[len] = NULLCHAR;
4863       } else {
4864         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4865       }
4866       SendToProgram(buf, cps);
4867     } else {
4868       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4869         AlphaRank(moveList[moveNum], 4);
4870         SendToProgram(moveList[moveNum], cps);
4871         AlphaRank(moveList[moveNum], 4); // and back
4872       } else
4873       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4874        * the engine. It would be nice to have a better way to identify castle
4875        * moves here. */
4876       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4877                                                                          && cps->useOOCastle) {
4878         int fromX = moveList[moveNum][0] - AAA;
4879         int fromY = moveList[moveNum][1] - ONE;
4880         int toX = moveList[moveNum][2] - AAA;
4881         int toY = moveList[moveNum][3] - ONE;
4882         if((boards[moveNum][fromY][fromX] == WhiteKing
4883             && boards[moveNum][toY][toX] == WhiteRook)
4884            || (boards[moveNum][fromY][fromX] == BlackKing
4885                && boards[moveNum][toY][toX] == BlackRook)) {
4886           if(toX > fromX) SendToProgram("O-O\n", cps);
4887           else SendToProgram("O-O-O\n", cps);
4888         }
4889         else SendToProgram(moveList[moveNum], cps);
4890       } else
4891       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4892         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4893           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4894           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4895                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4896         } else
4897           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4898                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4899         SendToProgram(buf, cps);
4900       }
4901       else SendToProgram(moveList[moveNum], cps);
4902       /* End of additions by Tord */
4903     }
4904
4905     /* [HGM] setting up the opening has brought engine in force mode! */
4906     /*       Send 'go' if we are in a mode where machine should play. */
4907     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4908         (gameMode == TwoMachinesPlay   ||
4909 #if ZIPPY
4910          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4911 #endif
4912          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4913         SendToProgram("go\n", cps);
4914   if (appData.debugMode) {
4915     fprintf(debugFP, "(extra)\n");
4916   }
4917     }
4918     setboardSpoiledMachineBlack = 0;
4919 }
4920
4921 void
4922 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4923 {
4924     char user_move[MSG_SIZ];
4925     char suffix[4];
4926
4927     if(gameInfo.variant == VariantSChess && promoChar) {
4928         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4929         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4930     } else suffix[0] = NULLCHAR;
4931
4932     switch (moveType) {
4933       default:
4934         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4935                 (int)moveType, fromX, fromY, toX, toY);
4936         DisplayError(user_move + strlen("say "), 0);
4937         break;
4938       case WhiteKingSideCastle:
4939       case BlackKingSideCastle:
4940       case WhiteQueenSideCastleWild:
4941       case BlackQueenSideCastleWild:
4942       /* PUSH Fabien */
4943       case WhiteHSideCastleFR:
4944       case BlackHSideCastleFR:
4945       /* POP Fabien */
4946         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4947         break;
4948       case WhiteQueenSideCastle:
4949       case BlackQueenSideCastle:
4950       case WhiteKingSideCastleWild:
4951       case BlackKingSideCastleWild:
4952       /* PUSH Fabien */
4953       case WhiteASideCastleFR:
4954       case BlackASideCastleFR:
4955       /* POP Fabien */
4956         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
4957         break;
4958       case WhiteNonPromotion:
4959       case BlackNonPromotion:
4960         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4961         break;
4962       case WhitePromotion:
4963       case BlackPromotion:
4964         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4965           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4966                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4967                 PieceToChar(WhiteFerz));
4968         else if(gameInfo.variant == VariantGreat)
4969           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4970                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4971                 PieceToChar(WhiteMan));
4972         else
4973           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4974                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4975                 promoChar);
4976         break;
4977       case WhiteDrop:
4978       case BlackDrop:
4979       drop:
4980         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4981                  ToUpper(PieceToChar((ChessSquare) fromX)),
4982                  AAA + toX, ONE + toY);
4983         break;
4984       case IllegalMove:  /* could be a variant we don't quite understand */
4985         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4986       case NormalMove:
4987       case WhiteCapturesEnPassant:
4988       case BlackCapturesEnPassant:
4989         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4990                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4991         break;
4992     }
4993     SendToICS(user_move);
4994     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4995         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4996 }
4997
4998 void
4999 UploadGameEvent ()
5000 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5001     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5002     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5003     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5004       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5005       return;
5006     }
5007     if(gameMode != IcsExamining) { // is this ever not the case?
5008         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5009
5010         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5011           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5012         } else { // on FICS we must first go to general examine mode
5013           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5014         }
5015         if(gameInfo.variant != VariantNormal) {
5016             // try figure out wild number, as xboard names are not always valid on ICS
5017             for(i=1; i<=36; i++) {
5018               snprintf(buf, MSG_SIZ, "wild/%d", i);
5019                 if(StringToVariant(buf) == gameInfo.variant) break;
5020             }
5021             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5022             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5023             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5024         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5025         SendToICS(ics_prefix);
5026         SendToICS(buf);
5027         if(startedFromSetupPosition || backwardMostMove != 0) {
5028           fen = PositionToFEN(backwardMostMove, NULL);
5029           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5030             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5031             SendToICS(buf);
5032           } else { // FICS: everything has to set by separate bsetup commands
5033             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5034             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5035             SendToICS(buf);
5036             if(!WhiteOnMove(backwardMostMove)) {
5037                 SendToICS("bsetup tomove black\n");
5038             }
5039             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5040             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5041             SendToICS(buf);
5042             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5043             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5044             SendToICS(buf);
5045             i = boards[backwardMostMove][EP_STATUS];
5046             if(i >= 0) { // set e.p.
5047               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5048                 SendToICS(buf);
5049             }
5050             bsetup++;
5051           }
5052         }
5053       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5054             SendToICS("bsetup done\n"); // switch to normal examining.
5055     }
5056     for(i = backwardMostMove; i<last; i++) {
5057         char buf[20];
5058         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5059         SendToICS(buf);
5060     }
5061     SendToICS(ics_prefix);
5062     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5063 }
5064
5065 void
5066 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5067 {
5068     if (rf == DROP_RANK) {
5069       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5070       sprintf(move, "%c@%c%c\n",
5071                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5072     } else {
5073         if (promoChar == 'x' || promoChar == NULLCHAR) {
5074           sprintf(move, "%c%c%c%c\n",
5075                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5076         } else {
5077             sprintf(move, "%c%c%c%c%c\n",
5078                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5079         }
5080     }
5081 }
5082
5083 void
5084 ProcessICSInitScript (FILE *f)
5085 {
5086     char buf[MSG_SIZ];
5087
5088     while (fgets(buf, MSG_SIZ, f)) {
5089         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5090     }
5091
5092     fclose(f);
5093 }
5094
5095
5096 static int lastX, lastY, selectFlag, dragging;
5097
5098 void
5099 Sweep (int step)
5100 {
5101     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5102     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5103     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5104     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5105     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5106     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5107     do {
5108         promoSweep -= step;
5109         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5110         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5111         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5112         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5113         if(!step) step = -1;
5114     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5115             appData.testLegality && (promoSweep == king ||
5116             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5117     ChangeDragPiece(promoSweep);
5118 }
5119
5120 int
5121 PromoScroll (int x, int y)
5122 {
5123   int step = 0;
5124
5125   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5126   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5127   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5128   if(!step) return FALSE;
5129   lastX = x; lastY = y;
5130   if((promoSweep < BlackPawn) == flipView) step = -step;
5131   if(step > 0) selectFlag = 1;
5132   if(!selectFlag) Sweep(step);
5133   return FALSE;
5134 }
5135
5136 void
5137 NextPiece (int step)
5138 {
5139     ChessSquare piece = boards[currentMove][toY][toX];
5140     do {
5141         pieceSweep -= step;
5142         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5143         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5144         if(!step) step = -1;
5145     } while(PieceToChar(pieceSweep) == '.');
5146     boards[currentMove][toY][toX] = pieceSweep;
5147     DrawPosition(FALSE, boards[currentMove]);
5148     boards[currentMove][toY][toX] = piece;
5149 }
5150 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5151 void
5152 AlphaRank (char *move, int n)
5153 {
5154 //    char *p = move, c; int x, y;
5155
5156     if (appData.debugMode) {
5157         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5158     }
5159
5160     if(move[1]=='*' &&
5161        move[2]>='0' && move[2]<='9' &&
5162        move[3]>='a' && move[3]<='x'    ) {
5163         move[1] = '@';
5164         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5165         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5166     } else
5167     if(move[0]>='0' && move[0]<='9' &&
5168        move[1]>='a' && move[1]<='x' &&
5169        move[2]>='0' && move[2]<='9' &&
5170        move[3]>='a' && move[3]<='x'    ) {
5171         /* input move, Shogi -> normal */
5172         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5173         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5174         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5175         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5176     } else
5177     if(move[1]=='@' &&
5178        move[3]>='0' && move[3]<='9' &&
5179        move[2]>='a' && move[2]<='x'    ) {
5180         move[1] = '*';
5181         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5182         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5183     } else
5184     if(
5185        move[0]>='a' && move[0]<='x' &&
5186        move[3]>='0' && move[3]<='9' &&
5187        move[2]>='a' && move[2]<='x'    ) {
5188          /* output move, normal -> Shogi */
5189         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5190         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5191         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5192         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5193         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5194     }
5195     if (appData.debugMode) {
5196         fprintf(debugFP, "   out = '%s'\n", move);
5197     }
5198 }
5199
5200 char yy_textstr[8000];
5201
5202 /* Parser for moves from gnuchess, ICS, or user typein box */
5203 Boolean
5204 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5205 {
5206     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5207
5208     switch (*moveType) {
5209       case WhitePromotion:
5210       case BlackPromotion:
5211       case WhiteNonPromotion:
5212       case BlackNonPromotion:
5213       case NormalMove:
5214       case WhiteCapturesEnPassant:
5215       case BlackCapturesEnPassant:
5216       case WhiteKingSideCastle:
5217       case WhiteQueenSideCastle:
5218       case BlackKingSideCastle:
5219       case BlackQueenSideCastle:
5220       case WhiteKingSideCastleWild:
5221       case WhiteQueenSideCastleWild:
5222       case BlackKingSideCastleWild:
5223       case BlackQueenSideCastleWild:
5224       /* Code added by Tord: */
5225       case WhiteHSideCastleFR:
5226       case WhiteASideCastleFR:
5227       case BlackHSideCastleFR:
5228       case BlackASideCastleFR:
5229       /* End of code added by Tord */
5230       case IllegalMove:         /* bug or odd chess variant */
5231         *fromX = currentMoveString[0] - AAA;
5232         *fromY = currentMoveString[1] - ONE;
5233         *toX = currentMoveString[2] - AAA;
5234         *toY = currentMoveString[3] - ONE;
5235         *promoChar = currentMoveString[4];
5236         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5237             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5238     if (appData.debugMode) {
5239         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5240     }
5241             *fromX = *fromY = *toX = *toY = 0;
5242             return FALSE;
5243         }
5244         if (appData.testLegality) {
5245           return (*moveType != IllegalMove);
5246         } else {
5247           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5248                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5249         }
5250
5251       case WhiteDrop:
5252       case BlackDrop:
5253         *fromX = *moveType == WhiteDrop ?
5254           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5255           (int) CharToPiece(ToLower(currentMoveString[0]));
5256         *fromY = DROP_RANK;
5257         *toX = currentMoveString[2] - AAA;
5258         *toY = currentMoveString[3] - ONE;
5259         *promoChar = NULLCHAR;
5260         return TRUE;
5261
5262       case AmbiguousMove:
5263       case ImpossibleMove:
5264       case EndOfFile:
5265       case ElapsedTime:
5266       case Comment:
5267       case PGNTag:
5268       case NAG:
5269       case WhiteWins:
5270       case BlackWins:
5271       case GameIsDrawn:
5272       default:
5273     if (appData.debugMode) {
5274         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5275     }
5276         /* bug? */
5277         *fromX = *fromY = *toX = *toY = 0;
5278         *promoChar = NULLCHAR;
5279         return FALSE;
5280     }
5281 }
5282
5283 Boolean pushed = FALSE;
5284 char *lastParseAttempt;
5285
5286 void
5287 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5288 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5289   int fromX, fromY, toX, toY; char promoChar;
5290   ChessMove moveType;
5291   Boolean valid;
5292   int nr = 0;
5293
5294   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5295     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5296     pushed = TRUE;
5297   }
5298   endPV = forwardMostMove;
5299   do {
5300     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5301     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5302     lastParseAttempt = pv;
5303     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5304 if(appData.debugMode){
5305 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);
5306 }
5307     if(!valid && nr == 0 &&
5308        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5309         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5310         // Hande case where played move is different from leading PV move
5311         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5312         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5313         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5314         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5315           endPV += 2; // if position different, keep this
5316           moveList[endPV-1][0] = fromX + AAA;
5317           moveList[endPV-1][1] = fromY + ONE;
5318           moveList[endPV-1][2] = toX + AAA;
5319           moveList[endPV-1][3] = toY + ONE;
5320           parseList[endPV-1][0] = NULLCHAR;
5321           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5322         }
5323       }
5324     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5325     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5326     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5327     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5328         valid++; // allow comments in PV
5329         continue;
5330     }
5331     nr++;
5332     if(endPV+1 > framePtr) break; // no space, truncate
5333     if(!valid) break;
5334     endPV++;
5335     CopyBoard(boards[endPV], boards[endPV-1]);
5336     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5337     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5338     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5339     CoordsToAlgebraic(boards[endPV - 1],
5340                              PosFlags(endPV - 1),
5341                              fromY, fromX, toY, toX, promoChar,
5342                              parseList[endPV - 1]);
5343   } while(valid);
5344   if(atEnd == 2) return; // used hidden, for PV conversion
5345   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5346   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5347   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5348                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5349   DrawPosition(TRUE, boards[currentMove]);
5350 }
5351
5352 int
5353 MultiPV (ChessProgramState *cps)
5354 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5355         int i;
5356         for(i=0; i<cps->nrOptions; i++)
5357             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5358                 return i;
5359         return -1;
5360 }
5361
5362 Boolean
5363 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5364 {
5365         int startPV, multi, lineStart, origIndex = index;
5366         char *p, buf2[MSG_SIZ];
5367
5368         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5369         lastX = x; lastY = y;
5370         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5371         lineStart = startPV = index;
5372         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5373         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5374         index = startPV;
5375         do{ while(buf[index] && buf[index] != '\n') index++;
5376         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5377         buf[index] = 0;
5378         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5379                 int n = first.option[multi].value;
5380                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5381                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5382                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5383                 first.option[multi].value = n;
5384                 *start = *end = 0;
5385                 return FALSE;
5386         }
5387         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5388         *start = startPV; *end = index-1;
5389         return TRUE;
5390 }
5391
5392 char *
5393 PvToSAN (char *pv)
5394 {
5395         static char buf[10*MSG_SIZ];
5396         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5397         *buf = NULLCHAR;
5398         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5399         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5400         for(i = forwardMostMove; i<endPV; i++){
5401             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5402             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5403             k += strlen(buf+k);
5404         }
5405         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5406         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5407         endPV = savedEnd;
5408         return buf;
5409 }
5410
5411 Boolean
5412 LoadPV (int x, int y)
5413 { // called on right mouse click to load PV
5414   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5415   lastX = x; lastY = y;
5416   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5417   return TRUE;
5418 }
5419
5420 void
5421 UnLoadPV ()
5422 {
5423   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5424   if(endPV < 0) return;
5425   endPV = -1;
5426   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5427         Boolean saveAnimate = appData.animate;
5428         if(pushed) {
5429             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5430                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5431             } else storedGames--; // abandon shelved tail of original game
5432         }
5433         pushed = FALSE;
5434         forwardMostMove = currentMove;
5435         currentMove = oldFMM;
5436         appData.animate = FALSE;
5437         ToNrEvent(forwardMostMove);
5438         appData.animate = saveAnimate;
5439   }
5440   currentMove = forwardMostMove;
5441   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5442   ClearPremoveHighlights();
5443   DrawPosition(TRUE, boards[currentMove]);
5444 }
5445
5446 void
5447 MovePV (int x, int y, int h)
5448 { // step through PV based on mouse coordinates (called on mouse move)
5449   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5450
5451   // we must somehow check if right button is still down (might be released off board!)
5452   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5453   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5454   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5455   if(!step) return;
5456   lastX = x; lastY = y;
5457
5458   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5459   if(endPV < 0) return;
5460   if(y < margin) step = 1; else
5461   if(y > h - margin) step = -1;
5462   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5463   currentMove += step;
5464   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5465   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5466                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5467   DrawPosition(FALSE, boards[currentMove]);
5468 }
5469
5470
5471 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5472 // All positions will have equal probability, but the current method will not provide a unique
5473 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5474 #define DARK 1
5475 #define LITE 2
5476 #define ANY 3
5477
5478 int squaresLeft[4];
5479 int piecesLeft[(int)BlackPawn];
5480 int seed, nrOfShuffles;
5481
5482 void
5483 GetPositionNumber ()
5484 {       // sets global variable seed
5485         int i;
5486
5487         seed = appData.defaultFrcPosition;
5488         if(seed < 0) { // randomize based on time for negative FRC position numbers
5489                 for(i=0; i<50; i++) seed += random();
5490                 seed = random() ^ random() >> 8 ^ random() << 8;
5491                 if(seed<0) seed = -seed;
5492         }
5493 }
5494
5495 int
5496 put (Board board, int pieceType, int rank, int n, int shade)
5497 // put the piece on the (n-1)-th empty squares of the given shade
5498 {
5499         int i;
5500
5501         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5502                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5503                         board[rank][i] = (ChessSquare) pieceType;
5504                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5505                         squaresLeft[ANY]--;
5506                         piecesLeft[pieceType]--;
5507                         return i;
5508                 }
5509         }
5510         return -1;
5511 }
5512
5513
5514 void
5515 AddOnePiece (Board board, int pieceType, int rank, int shade)
5516 // calculate where the next piece goes, (any empty square), and put it there
5517 {
5518         int i;
5519
5520         i = seed % squaresLeft[shade];
5521         nrOfShuffles *= squaresLeft[shade];
5522         seed /= squaresLeft[shade];
5523         put(board, pieceType, rank, i, shade);
5524 }
5525
5526 void
5527 AddTwoPieces (Board board, int pieceType, int rank)
5528 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5529 {
5530         int i, n=squaresLeft[ANY], j=n-1, k;
5531
5532         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5533         i = seed % k;  // pick one
5534         nrOfShuffles *= k;
5535         seed /= k;
5536         while(i >= j) i -= j--;
5537         j = n - 1 - j; i += j;
5538         put(board, pieceType, rank, j, ANY);
5539         put(board, pieceType, rank, i, ANY);
5540 }
5541
5542 void
5543 SetUpShuffle (Board board, int number)
5544 {
5545         int i, p, first=1;
5546
5547         GetPositionNumber(); nrOfShuffles = 1;
5548
5549         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5550         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5551         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5552
5553         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5554
5555         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5556             p = (int) board[0][i];
5557             if(p < (int) BlackPawn) piecesLeft[p] ++;
5558             board[0][i] = EmptySquare;
5559         }
5560
5561         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5562             // shuffles restricted to allow normal castling put KRR first
5563             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5564                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5565             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5566                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5567             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5568                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5569             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5570                 put(board, WhiteRook, 0, 0, ANY);
5571             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5572         }
5573
5574         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5575             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5576             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5577                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5578                 while(piecesLeft[p] >= 2) {
5579                     AddOnePiece(board, p, 0, LITE);
5580                     AddOnePiece(board, p, 0, DARK);
5581                 }
5582                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5583             }
5584
5585         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5586             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5587             // but we leave King and Rooks for last, to possibly obey FRC restriction
5588             if(p == (int)WhiteRook) continue;
5589             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5590             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5591         }
5592
5593         // now everything is placed, except perhaps King (Unicorn) and Rooks
5594
5595         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5596             // Last King gets castling rights
5597             while(piecesLeft[(int)WhiteUnicorn]) {
5598                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5599                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5600             }
5601
5602             while(piecesLeft[(int)WhiteKing]) {
5603                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5604                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5605             }
5606
5607
5608         } else {
5609             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5610             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5611         }
5612
5613         // Only Rooks can be left; simply place them all
5614         while(piecesLeft[(int)WhiteRook]) {
5615                 i = put(board, WhiteRook, 0, 0, ANY);
5616                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5617                         if(first) {
5618                                 first=0;
5619                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5620                         }
5621                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5622                 }
5623         }
5624         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5625             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5626         }
5627
5628         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5629 }
5630
5631 int
5632 SetCharTable (char *table, const char * map)
5633 /* [HGM] moved here from winboard.c because of its general usefulness */
5634 /*       Basically a safe strcpy that uses the last character as King */
5635 {
5636     int result = FALSE; int NrPieces;
5637
5638     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5639                     && NrPieces >= 12 && !(NrPieces&1)) {
5640         int i; /* [HGM] Accept even length from 12 to 34 */
5641
5642         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5643         for( i=0; i<NrPieces/2-1; i++ ) {
5644             table[i] = map[i];
5645             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5646         }
5647         table[(int) WhiteKing]  = map[NrPieces/2-1];
5648         table[(int) BlackKing]  = map[NrPieces-1];
5649
5650         result = TRUE;
5651     }
5652
5653     return result;
5654 }
5655
5656 void
5657 Prelude (Board board)
5658 {       // [HGM] superchess: random selection of exo-pieces
5659         int i, j, k; ChessSquare p;
5660         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5661
5662         GetPositionNumber(); // use FRC position number
5663
5664         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5665             SetCharTable(pieceToChar, appData.pieceToCharTable);
5666             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5667                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5668         }
5669
5670         j = seed%4;                 seed /= 4;
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 >= j); seed /= 3;
5675         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = 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%3;                 seed /= 3;
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%2 + (seed%2 >= j); seed /= 2;
5683         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5684         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5685         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5686         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5687         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5688         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5689         put(board, exoPieces[0],    0, 0, ANY);
5690         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5691 }
5692
5693 void
5694 InitPosition (int redraw)
5695 {
5696     ChessSquare (* pieces)[BOARD_FILES];
5697     int i, j, pawnRow, overrule,
5698     oldx = gameInfo.boardWidth,
5699     oldy = gameInfo.boardHeight,
5700     oldh = gameInfo.holdingsWidth;
5701     static int oldv;
5702
5703     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5704
5705     /* [AS] Initialize pv info list [HGM] and game status */
5706     {
5707         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5708             pvInfoList[i].depth = 0;
5709             boards[i][EP_STATUS] = EP_NONE;
5710             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5711         }
5712
5713         initialRulePlies = 0; /* 50-move counter start */
5714
5715         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5716         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5717     }
5718
5719
5720     /* [HGM] logic here is completely changed. In stead of full positions */
5721     /* the initialized data only consist of the two backranks. The switch */
5722     /* selects which one we will use, which is than copied to the Board   */
5723     /* initialPosition, which for the rest is initialized by Pawns and    */
5724     /* empty squares. This initial position is then copied to boards[0],  */
5725     /* possibly after shuffling, so that it remains available.            */
5726
5727     gameInfo.holdingsWidth = 0; /* default board sizes */
5728     gameInfo.boardWidth    = 8;
5729     gameInfo.boardHeight   = 8;
5730     gameInfo.holdingsSize  = 0;
5731     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5732     for(i=0; i<BOARD_FILES-2; i++)
5733       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5734     initialPosition[EP_STATUS] = EP_NONE;
5735     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5736     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5737          SetCharTable(pieceNickName, appData.pieceNickNames);
5738     else SetCharTable(pieceNickName, "............");
5739     pieces = FIDEArray;
5740
5741     switch (gameInfo.variant) {
5742     case VariantFischeRandom:
5743       shuffleOpenings = TRUE;
5744     default:
5745       break;
5746     case VariantShatranj:
5747       pieces = ShatranjArray;
5748       nrCastlingRights = 0;
5749       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5750       break;
5751     case VariantMakruk:
5752       pieces = makrukArray;
5753       nrCastlingRights = 0;
5754       startedFromSetupPosition = TRUE;
5755       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5756       break;
5757     case VariantTwoKings:
5758       pieces = twoKingsArray;
5759       break;
5760     case VariantGrand:
5761       pieces = GrandArray;
5762       nrCastlingRights = 0;
5763       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5764       gameInfo.boardWidth = 10;
5765       gameInfo.boardHeight = 10;
5766       gameInfo.holdingsSize = 7;
5767       break;
5768     case VariantCapaRandom:
5769       shuffleOpenings = TRUE;
5770     case VariantCapablanca:
5771       pieces = CapablancaArray;
5772       gameInfo.boardWidth = 10;
5773       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5774       break;
5775     case VariantGothic:
5776       pieces = GothicArray;
5777       gameInfo.boardWidth = 10;
5778       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5779       break;
5780     case VariantSChess:
5781       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5782       gameInfo.holdingsSize = 7;
5783       break;
5784     case VariantJanus:
5785       pieces = JanusArray;
5786       gameInfo.boardWidth = 10;
5787       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5788       nrCastlingRights = 6;
5789         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5790         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5791         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5792         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5793         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5794         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5795       break;
5796     case VariantFalcon:
5797       pieces = FalconArray;
5798       gameInfo.boardWidth = 10;
5799       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5800       break;
5801     case VariantXiangqi:
5802       pieces = XiangqiArray;
5803       gameInfo.boardWidth  = 9;
5804       gameInfo.boardHeight = 10;
5805       nrCastlingRights = 0;
5806       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5807       break;
5808     case VariantShogi:
5809       pieces = ShogiArray;
5810       gameInfo.boardWidth  = 9;
5811       gameInfo.boardHeight = 9;
5812       gameInfo.holdingsSize = 7;
5813       nrCastlingRights = 0;
5814       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5815       break;
5816     case VariantCourier:
5817       pieces = CourierArray;
5818       gameInfo.boardWidth  = 12;
5819       nrCastlingRights = 0;
5820       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5821       break;
5822     case VariantKnightmate:
5823       pieces = KnightmateArray;
5824       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5825       break;
5826     case VariantSpartan:
5827       pieces = SpartanArray;
5828       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5829       break;
5830     case VariantFairy:
5831       pieces = fairyArray;
5832       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5833       break;
5834     case VariantGreat:
5835       pieces = GreatArray;
5836       gameInfo.boardWidth = 10;
5837       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5838       gameInfo.holdingsSize = 8;
5839       break;
5840     case VariantSuper:
5841       pieces = FIDEArray;
5842       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5843       gameInfo.holdingsSize = 8;
5844       startedFromSetupPosition = TRUE;
5845       break;
5846     case VariantCrazyhouse:
5847     case VariantBughouse:
5848       pieces = FIDEArray;
5849       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5850       gameInfo.holdingsSize = 5;
5851       break;
5852     case VariantWildCastle:
5853       pieces = FIDEArray;
5854       /* !!?shuffle with kings guaranteed to be on d or e file */
5855       shuffleOpenings = 1;
5856       break;
5857     case VariantNoCastle:
5858       pieces = FIDEArray;
5859       nrCastlingRights = 0;
5860       /* !!?unconstrained back-rank shuffle */
5861       shuffleOpenings = 1;
5862       break;
5863     }
5864
5865     overrule = 0;
5866     if(appData.NrFiles >= 0) {
5867         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5868         gameInfo.boardWidth = appData.NrFiles;
5869     }
5870     if(appData.NrRanks >= 0) {
5871         gameInfo.boardHeight = appData.NrRanks;
5872     }
5873     if(appData.holdingsSize >= 0) {
5874         i = appData.holdingsSize;
5875         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5876         gameInfo.holdingsSize = i;
5877     }
5878     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5879     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5880         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5881
5882     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5883     if(pawnRow < 1) pawnRow = 1;
5884     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5885
5886     /* User pieceToChar list overrules defaults */
5887     if(appData.pieceToCharTable != NULL)
5888         SetCharTable(pieceToChar, appData.pieceToCharTable);
5889
5890     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5891
5892         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5893             s = (ChessSquare) 0; /* account holding counts in guard band */
5894         for( i=0; i<BOARD_HEIGHT; i++ )
5895             initialPosition[i][j] = s;
5896
5897         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5898         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5899         initialPosition[pawnRow][j] = WhitePawn;
5900         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5901         if(gameInfo.variant == VariantXiangqi) {
5902             if(j&1) {
5903                 initialPosition[pawnRow][j] =
5904                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5905                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5906                    initialPosition[2][j] = WhiteCannon;
5907                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5908                 }
5909             }
5910         }
5911         if(gameInfo.variant == VariantGrand) {
5912             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5913                initialPosition[0][j] = WhiteRook;
5914                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5915             }
5916         }
5917         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5918     }
5919     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5920
5921             j=BOARD_LEFT+1;
5922             initialPosition[1][j] = WhiteBishop;
5923             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5924             j=BOARD_RGHT-2;
5925             initialPosition[1][j] = WhiteRook;
5926             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5927     }
5928
5929     if( nrCastlingRights == -1) {
5930         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5931         /*       This sets default castling rights from none to normal corners   */
5932         /* Variants with other castling rights must set them themselves above    */
5933         nrCastlingRights = 6;
5934
5935         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5936         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5937         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5938         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5939         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5940         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5941      }
5942
5943      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5944      if(gameInfo.variant == VariantGreat) { // promotion commoners
5945         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5946         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5947         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5948         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5949      }
5950      if( gameInfo.variant == VariantSChess ) {
5951       initialPosition[1][0] = BlackMarshall;
5952       initialPosition[2][0] = BlackAngel;
5953       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5954       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5955       initialPosition[1][1] = initialPosition[2][1] = 
5956       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5957      }
5958   if (appData.debugMode) {
5959     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5960   }
5961     if(shuffleOpenings) {
5962         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5963         startedFromSetupPosition = TRUE;
5964     }
5965     if(startedFromPositionFile) {
5966       /* [HGM] loadPos: use PositionFile for every new game */
5967       CopyBoard(initialPosition, filePosition);
5968       for(i=0; i<nrCastlingRights; i++)
5969           initialRights[i] = filePosition[CASTLING][i];
5970       startedFromSetupPosition = TRUE;
5971     }
5972
5973     CopyBoard(boards[0], initialPosition);
5974
5975     if(oldx != gameInfo.boardWidth ||
5976        oldy != gameInfo.boardHeight ||
5977        oldv != gameInfo.variant ||
5978        oldh != gameInfo.holdingsWidth
5979                                          )
5980             InitDrawingSizes(-2 ,0);
5981
5982     oldv = gameInfo.variant;
5983     if (redraw)
5984       DrawPosition(TRUE, boards[currentMove]);
5985 }
5986
5987 void
5988 SendBoard (ChessProgramState *cps, int moveNum)
5989 {
5990     char message[MSG_SIZ];
5991
5992     if (cps->useSetboard) {
5993       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5994       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5995       SendToProgram(message, cps);
5996       free(fen);
5997
5998     } else {
5999       ChessSquare *bp;
6000       int i, j, left=0, right=BOARD_WIDTH;
6001       /* Kludge to set black to move, avoiding the troublesome and now
6002        * deprecated "black" command.
6003        */
6004       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6005         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6006
6007       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6008
6009       SendToProgram("edit\n", cps);
6010       SendToProgram("#\n", cps);
6011       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6012         bp = &boards[moveNum][i][left];
6013         for (j = left; j < right; j++, bp++) {
6014           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6015           if ((int) *bp < (int) BlackPawn) {
6016             if(j == BOARD_RGHT+1)
6017                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6018             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6019             if(message[0] == '+' || message[0] == '~') {
6020               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6021                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6022                         AAA + j, ONE + i);
6023             }
6024             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6025                 message[1] = BOARD_RGHT   - 1 - j + '1';
6026                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6027             }
6028             SendToProgram(message, cps);
6029           }
6030         }
6031       }
6032
6033       SendToProgram("c\n", cps);
6034       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6035         bp = &boards[moveNum][i][left];
6036         for (j = left; j < right; j++, bp++) {
6037           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6038           if (((int) *bp != (int) EmptySquare)
6039               && ((int) *bp >= (int) BlackPawn)) {
6040             if(j == BOARD_LEFT-2)
6041                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6042             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6043                     AAA + j, ONE + i);
6044             if(message[0] == '+' || message[0] == '~') {
6045               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6046                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6047                         AAA + j, ONE + i);
6048             }
6049             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6050                 message[1] = BOARD_RGHT   - 1 - j + '1';
6051                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6052             }
6053             SendToProgram(message, cps);
6054           }
6055         }
6056       }
6057
6058       SendToProgram(".\n", cps);
6059     }
6060     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6061 }
6062
6063 ChessSquare
6064 DefaultPromoChoice (int white)
6065 {
6066     ChessSquare result;
6067     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6068         result = WhiteFerz; // no choice
6069     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6070         result= WhiteKing; // in Suicide Q is the last thing we want
6071     else if(gameInfo.variant == VariantSpartan)
6072         result = white ? WhiteQueen : WhiteAngel;
6073     else result = WhiteQueen;
6074     if(!white) result = WHITE_TO_BLACK result;
6075     return result;
6076 }
6077
6078 static int autoQueen; // [HGM] oneclick
6079
6080 int
6081 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6082 {
6083     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6084     /* [HGM] add Shogi promotions */
6085     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6086     ChessSquare piece;
6087     ChessMove moveType;
6088     Boolean premove;
6089
6090     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6091     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6092
6093     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6094       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6095         return FALSE;
6096
6097     piece = boards[currentMove][fromY][fromX];
6098     if(gameInfo.variant == VariantShogi) {
6099         promotionZoneSize = BOARD_HEIGHT/3;
6100         highestPromotingPiece = (int)WhiteFerz;
6101     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6102         promotionZoneSize = 3;
6103     }
6104
6105     // Treat Lance as Pawn when it is not representing Amazon
6106     if(gameInfo.variant != VariantSuper) {
6107         if(piece == WhiteLance) piece = WhitePawn; else
6108         if(piece == BlackLance) piece = BlackPawn;
6109     }
6110
6111     // next weed out all moves that do not touch the promotion zone at all
6112     if((int)piece >= BlackPawn) {
6113         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6114              return FALSE;
6115         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6116     } else {
6117         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6118            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6119     }
6120
6121     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6122
6123     // weed out mandatory Shogi promotions
6124     if(gameInfo.variant == VariantShogi) {
6125         if(piece >= BlackPawn) {
6126             if(toY == 0 && piece == BlackPawn ||
6127                toY == 0 && piece == BlackQueen ||
6128                toY <= 1 && piece == BlackKnight) {
6129                 *promoChoice = '+';
6130                 return FALSE;
6131             }
6132         } else {
6133             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6134                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6135                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6136                 *promoChoice = '+';
6137                 return FALSE;
6138             }
6139         }
6140     }
6141
6142     // weed out obviously illegal Pawn moves
6143     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6144         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6145         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6146         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6147         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6148         // note we are not allowed to test for valid (non-)capture, due to premove
6149     }
6150
6151     // we either have a choice what to promote to, or (in Shogi) whether to promote
6152     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6153         *promoChoice = PieceToChar(BlackFerz);  // no choice
6154         return FALSE;
6155     }
6156     // no sense asking what we must promote to if it is going to explode...
6157     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6158         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6159         return FALSE;
6160     }
6161     // give caller the default choice even if we will not make it
6162     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6163     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6164     if(        sweepSelect && gameInfo.variant != VariantGreat
6165                            && gameInfo.variant != VariantGrand
6166                            && gameInfo.variant != VariantSuper) return FALSE;
6167     if(autoQueen) return FALSE; // predetermined
6168
6169     // suppress promotion popup on illegal moves that are not premoves
6170     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6171               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6172     if(appData.testLegality && !premove) {
6173         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6174                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6175         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6176             return FALSE;
6177     }
6178
6179     return TRUE;
6180 }
6181
6182 int
6183 InPalace (int row, int column)
6184 {   /* [HGM] for Xiangqi */
6185     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6186          column < (BOARD_WIDTH + 4)/2 &&
6187          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6188     return FALSE;
6189 }
6190
6191 int
6192 PieceForSquare (int x, int y)
6193 {
6194   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6195      return -1;
6196   else
6197      return boards[currentMove][y][x];
6198 }
6199
6200 int
6201 OKToStartUserMove (int x, int y)
6202 {
6203     ChessSquare from_piece;
6204     int white_piece;
6205
6206     if (matchMode) return FALSE;
6207     if (gameMode == EditPosition) return TRUE;
6208
6209     if (x >= 0 && y >= 0)
6210       from_piece = boards[currentMove][y][x];
6211     else
6212       from_piece = EmptySquare;
6213
6214     if (from_piece == EmptySquare) return FALSE;
6215
6216     white_piece = (int)from_piece >= (int)WhitePawn &&
6217       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6218
6219     switch (gameMode) {
6220       case AnalyzeFile:
6221       case TwoMachinesPlay:
6222       case EndOfGame:
6223         return FALSE;
6224
6225       case IcsObserving:
6226       case IcsIdle:
6227         return FALSE;
6228
6229       case MachinePlaysWhite:
6230       case IcsPlayingBlack:
6231         if (appData.zippyPlay) return FALSE;
6232         if (white_piece) {
6233             DisplayMoveError(_("You are playing Black"));
6234             return FALSE;
6235         }
6236         break;
6237
6238       case MachinePlaysBlack:
6239       case IcsPlayingWhite:
6240         if (appData.zippyPlay) return FALSE;
6241         if (!white_piece) {
6242             DisplayMoveError(_("You are playing White"));
6243             return FALSE;
6244         }
6245         break;
6246
6247       case PlayFromGameFile:
6248             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6249       case EditGame:
6250         if (!white_piece && WhiteOnMove(currentMove)) {
6251             DisplayMoveError(_("It is White's turn"));
6252             return FALSE;
6253         }
6254         if (white_piece && !WhiteOnMove(currentMove)) {
6255             DisplayMoveError(_("It is Black's turn"));
6256             return FALSE;
6257         }
6258         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6259             /* Editing correspondence game history */
6260             /* Could disallow this or prompt for confirmation */
6261             cmailOldMove = -1;
6262         }
6263         break;
6264
6265       case BeginningOfGame:
6266         if (appData.icsActive) return FALSE;
6267         if (!appData.noChessProgram) {
6268             if (!white_piece) {
6269                 DisplayMoveError(_("You are playing White"));
6270                 return FALSE;
6271             }
6272         }
6273         break;
6274
6275       case Training:
6276         if (!white_piece && WhiteOnMove(currentMove)) {
6277             DisplayMoveError(_("It is White's turn"));
6278             return FALSE;
6279         }
6280         if (white_piece && !WhiteOnMove(currentMove)) {
6281             DisplayMoveError(_("It is Black's turn"));
6282             return FALSE;
6283         }
6284         break;
6285
6286       default:
6287       case IcsExamining:
6288         break;
6289     }
6290     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6291         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6292         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6293         && gameMode != AnalyzeFile && gameMode != Training) {
6294         DisplayMoveError(_("Displayed position is not current"));
6295         return FALSE;
6296     }
6297     return TRUE;
6298 }
6299
6300 Boolean
6301 OnlyMove (int *x, int *y, Boolean captures) 
6302 {
6303     DisambiguateClosure cl;
6304     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6305     switch(gameMode) {
6306       case MachinePlaysBlack:
6307       case IcsPlayingWhite:
6308       case BeginningOfGame:
6309         if(!WhiteOnMove(currentMove)) return FALSE;
6310         break;
6311       case MachinePlaysWhite:
6312       case IcsPlayingBlack:
6313         if(WhiteOnMove(currentMove)) return FALSE;
6314         break;
6315       case EditGame:
6316         break;
6317       default:
6318         return FALSE;
6319     }
6320     cl.pieceIn = EmptySquare;
6321     cl.rfIn = *y;
6322     cl.ffIn = *x;
6323     cl.rtIn = -1;
6324     cl.ftIn = -1;
6325     cl.promoCharIn = NULLCHAR;
6326     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6327     if( cl.kind == NormalMove ||
6328         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6329         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6330         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6331       fromX = cl.ff;
6332       fromY = cl.rf;
6333       *x = cl.ft;
6334       *y = cl.rt;
6335       return TRUE;
6336     }
6337     if(cl.kind != ImpossibleMove) return FALSE;
6338     cl.pieceIn = EmptySquare;
6339     cl.rfIn = -1;
6340     cl.ffIn = -1;
6341     cl.rtIn = *y;
6342     cl.ftIn = *x;
6343     cl.promoCharIn = NULLCHAR;
6344     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6345     if( cl.kind == NormalMove ||
6346         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6347         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6348         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6349       fromX = cl.ff;
6350       fromY = cl.rf;
6351       *x = cl.ft;
6352       *y = cl.rt;
6353       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6354       return TRUE;
6355     }
6356     return FALSE;
6357 }
6358
6359 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6360 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6361 int lastLoadGameUseList = FALSE;
6362 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6363 ChessMove lastLoadGameStart = EndOfFile;
6364
6365 void
6366 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6367 {
6368     ChessMove moveType;
6369     ChessSquare pdown, pup;
6370
6371     /* Check if the user is playing in turn.  This is complicated because we
6372        let the user "pick up" a piece before it is his turn.  So the piece he
6373        tried to pick up may have been captured by the time he puts it down!
6374        Therefore we use the color the user is supposed to be playing in this
6375        test, not the color of the piece that is currently on the starting
6376        square---except in EditGame mode, where the user is playing both
6377        sides; fortunately there the capture race can't happen.  (It can
6378        now happen in IcsExamining mode, but that's just too bad.  The user
6379        will get a somewhat confusing message in that case.)
6380        */
6381
6382     switch (gameMode) {
6383       case AnalyzeFile:
6384       case TwoMachinesPlay:
6385       case EndOfGame:
6386       case IcsObserving:
6387       case IcsIdle:
6388         /* We switched into a game mode where moves are not accepted,
6389            perhaps while the mouse button was down. */
6390         return;
6391
6392       case MachinePlaysWhite:
6393         /* User is moving for Black */
6394         if (WhiteOnMove(currentMove)) {
6395             DisplayMoveError(_("It is White's turn"));
6396             return;
6397         }
6398         break;
6399
6400       case MachinePlaysBlack:
6401         /* User is moving for White */
6402         if (!WhiteOnMove(currentMove)) {
6403             DisplayMoveError(_("It is Black's turn"));
6404             return;
6405         }
6406         break;
6407
6408       case PlayFromGameFile:
6409             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6410       case EditGame:
6411       case IcsExamining:
6412       case BeginningOfGame:
6413       case AnalyzeMode:
6414       case Training:
6415         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6416         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6417             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6418             /* User is moving for Black */
6419             if (WhiteOnMove(currentMove)) {
6420                 DisplayMoveError(_("It is White's turn"));
6421                 return;
6422             }
6423         } else {
6424             /* User is moving for White */
6425             if (!WhiteOnMove(currentMove)) {
6426                 DisplayMoveError(_("It is Black's turn"));
6427                 return;
6428             }
6429         }
6430         break;
6431
6432       case IcsPlayingBlack:
6433         /* User is moving for Black */
6434         if (WhiteOnMove(currentMove)) {
6435             if (!appData.premove) {
6436                 DisplayMoveError(_("It is White's turn"));
6437             } else if (toX >= 0 && toY >= 0) {
6438                 premoveToX = toX;
6439                 premoveToY = toY;
6440                 premoveFromX = fromX;
6441                 premoveFromY = fromY;
6442                 premovePromoChar = promoChar;
6443                 gotPremove = 1;
6444                 if (appData.debugMode)
6445                     fprintf(debugFP, "Got premove: fromX %d,"
6446                             "fromY %d, toX %d, toY %d\n",
6447                             fromX, fromY, toX, toY);
6448             }
6449             return;
6450         }
6451         break;
6452
6453       case IcsPlayingWhite:
6454         /* User is moving for White */
6455         if (!WhiteOnMove(currentMove)) {
6456             if (!appData.premove) {
6457                 DisplayMoveError(_("It is Black's turn"));
6458             } else if (toX >= 0 && toY >= 0) {
6459                 premoveToX = toX;
6460                 premoveToY = toY;
6461                 premoveFromX = fromX;
6462                 premoveFromY = fromY;
6463                 premovePromoChar = promoChar;
6464                 gotPremove = 1;
6465                 if (appData.debugMode)
6466                     fprintf(debugFP, "Got premove: fromX %d,"
6467                             "fromY %d, toX %d, toY %d\n",
6468                             fromX, fromY, toX, toY);
6469             }
6470             return;
6471         }
6472         break;
6473
6474       default:
6475         break;
6476
6477       case EditPosition:
6478         /* EditPosition, empty square, or different color piece;
6479            click-click move is possible */
6480         if (toX == -2 || toY == -2) {
6481             boards[0][fromY][fromX] = EmptySquare;
6482             DrawPosition(FALSE, boards[currentMove]);
6483             return;
6484         } else if (toX >= 0 && toY >= 0) {
6485             boards[0][toY][toX] = boards[0][fromY][fromX];
6486             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6487                 if(boards[0][fromY][0] != EmptySquare) {
6488                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6489                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6490                 }
6491             } else
6492             if(fromX == BOARD_RGHT+1) {
6493                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6494                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6495                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6496                 }
6497             } else
6498             boards[0][fromY][fromX] = EmptySquare;
6499             DrawPosition(FALSE, boards[currentMove]);
6500             return;
6501         }
6502         return;
6503     }
6504
6505     if(toX < 0 || toY < 0) return;
6506     pdown = boards[currentMove][fromY][fromX];
6507     pup = boards[currentMove][toY][toX];
6508
6509     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6510     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6511          if( pup != EmptySquare ) return;
6512          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6513            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6514                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6515            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6516            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6517            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6518            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6519          fromY = DROP_RANK;
6520     }
6521
6522     /* [HGM] always test for legality, to get promotion info */
6523     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6524                                          fromY, fromX, toY, toX, promoChar);
6525
6526     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6527
6528     /* [HGM] but possibly ignore an IllegalMove result */
6529     if (appData.testLegality) {
6530         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6531             DisplayMoveError(_("Illegal move"));
6532             return;
6533         }
6534     }
6535
6536     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6537 }
6538
6539 /* Common tail of UserMoveEvent and DropMenuEvent */
6540 int
6541 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6542 {
6543     char *bookHit = 0;
6544
6545     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6546         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6547         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6548         if(WhiteOnMove(currentMove)) {
6549             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6550         } else {
6551             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6552         }
6553     }
6554
6555     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6556        move type in caller when we know the move is a legal promotion */
6557     if(moveType == NormalMove && promoChar)
6558         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6559
6560     /* [HGM] <popupFix> The following if has been moved here from
6561        UserMoveEvent(). Because it seemed to belong here (why not allow
6562        piece drops in training games?), and because it can only be
6563        performed after it is known to what we promote. */
6564     if (gameMode == Training) {
6565       /* compare the move played on the board to the next move in the
6566        * game. If they match, display the move and the opponent's response.
6567        * If they don't match, display an error message.
6568        */
6569       int saveAnimate;
6570       Board testBoard;
6571       CopyBoard(testBoard, boards[currentMove]);
6572       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6573
6574       if (CompareBoards(testBoard, boards[currentMove+1])) {
6575         ForwardInner(currentMove+1);
6576
6577         /* Autoplay the opponent's response.
6578          * if appData.animate was TRUE when Training mode was entered,
6579          * the response will be animated.
6580          */
6581         saveAnimate = appData.animate;
6582         appData.animate = animateTraining;
6583         ForwardInner(currentMove+1);
6584         appData.animate = saveAnimate;
6585
6586         /* check for the end of the game */
6587         if (currentMove >= forwardMostMove) {
6588           gameMode = PlayFromGameFile;
6589           ModeHighlight();
6590           SetTrainingModeOff();
6591           DisplayInformation(_("End of game"));
6592         }
6593       } else {
6594         DisplayError(_("Incorrect move"), 0);
6595       }
6596       return 1;
6597     }
6598
6599   /* Ok, now we know that the move is good, so we can kill
6600      the previous line in Analysis Mode */
6601   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6602                                 && currentMove < forwardMostMove) {
6603     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6604     else forwardMostMove = currentMove;
6605   }
6606
6607   /* If we need the chess program but it's dead, restart it */
6608   ResurrectChessProgram();
6609
6610   /* A user move restarts a paused game*/
6611   if (pausing)
6612     PauseEvent();
6613
6614   thinkOutput[0] = NULLCHAR;
6615
6616   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6617
6618   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6619     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6620     return 1;
6621   }
6622
6623   if (gameMode == BeginningOfGame) {
6624     if (appData.noChessProgram) {
6625       gameMode = EditGame;
6626       SetGameInfo();
6627     } else {
6628       char buf[MSG_SIZ];
6629       gameMode = MachinePlaysBlack;
6630       StartClocks();
6631       SetGameInfo();
6632       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6633       DisplayTitle(buf);
6634       if (first.sendName) {
6635         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6636         SendToProgram(buf, &first);
6637       }
6638       StartClocks();
6639     }
6640     ModeHighlight();
6641   }
6642
6643   /* Relay move to ICS or chess engine */
6644   if (appData.icsActive) {
6645     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6646         gameMode == IcsExamining) {
6647       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6648         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6649         SendToICS("draw ");
6650         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6651       }
6652       // also send plain move, in case ICS does not understand atomic claims
6653       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6654       ics_user_moved = 1;
6655     }
6656   } else {
6657     if (first.sendTime && (gameMode == BeginningOfGame ||
6658                            gameMode == MachinePlaysWhite ||
6659                            gameMode == MachinePlaysBlack)) {
6660       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6661     }
6662     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6663          // [HGM] book: if program might be playing, let it use book
6664         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6665         first.maybeThinking = TRUE;
6666     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6667         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6668         SendBoard(&first, currentMove+1);
6669     } else SendMoveToProgram(forwardMostMove-1, &first);
6670     if (currentMove == cmailOldMove + 1) {
6671       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6672     }
6673   }
6674
6675   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6676
6677   switch (gameMode) {
6678   case EditGame:
6679     if(appData.testLegality)
6680     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6681     case MT_NONE:
6682     case MT_CHECK:
6683       break;
6684     case MT_CHECKMATE:
6685     case MT_STAINMATE:
6686       if (WhiteOnMove(currentMove)) {
6687         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6688       } else {
6689         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6690       }
6691       break;
6692     case MT_STALEMATE:
6693       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6694       break;
6695     }
6696     break;
6697
6698   case MachinePlaysBlack:
6699   case MachinePlaysWhite:
6700     /* disable certain menu options while machine is thinking */
6701     SetMachineThinkingEnables();
6702     break;
6703
6704   default:
6705     break;
6706   }
6707
6708   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6709   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6710
6711   if(bookHit) { // [HGM] book: simulate book reply
6712         static char bookMove[MSG_SIZ]; // a bit generous?
6713
6714         programStats.nodes = programStats.depth = programStats.time =
6715         programStats.score = programStats.got_only_move = 0;
6716         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6717
6718         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6719         strcat(bookMove, bookHit);
6720         HandleMachineMove(bookMove, &first);
6721   }
6722   return 1;
6723 }
6724
6725 void
6726 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6727 {
6728     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6729     Markers *m = (Markers *) closure;
6730     if(rf == fromY && ff == fromX)
6731         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6732                          || kind == WhiteCapturesEnPassant
6733                          || kind == BlackCapturesEnPassant);
6734     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6735 }
6736
6737 void
6738 MarkTargetSquares (int clear)
6739 {
6740   int x, y;
6741   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6742      !appData.testLegality || gameMode == EditPosition) return;
6743   if(clear) {
6744     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6745   } else {
6746     int capt = 0;
6747     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6748     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6749       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6750       if(capt)
6751       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6752     }
6753   }
6754   DrawPosition(TRUE, NULL);
6755 }
6756
6757 int
6758 Explode (Board board, int fromX, int fromY, int toX, int toY)
6759 {
6760     if(gameInfo.variant == VariantAtomic &&
6761        (board[toY][toX] != EmptySquare ||                     // capture?
6762         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6763                          board[fromY][fromX] == BlackPawn   )
6764       )) {
6765         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6766         return TRUE;
6767     }
6768     return FALSE;
6769 }
6770
6771 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6772
6773 int
6774 CanPromote (ChessSquare piece, int y)
6775 {
6776         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6777         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6778         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6779            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6780            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6781                                                   gameInfo.variant == VariantMakruk) return FALSE;
6782         return (piece == BlackPawn && y == 1 ||
6783                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6784                 piece == BlackLance && y == 1 ||
6785                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6786 }
6787
6788 void
6789 LeftClick (ClickType clickType, int xPix, int yPix)
6790 {
6791     int x, y;
6792     Boolean saveAnimate;
6793     static int second = 0, promotionChoice = 0, clearFlag = 0;
6794     char promoChoice = NULLCHAR;
6795     ChessSquare piece;
6796
6797     if(appData.seekGraph && appData.icsActive && loggedOn &&
6798         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6799         SeekGraphClick(clickType, xPix, yPix, 0);
6800         return;
6801     }
6802
6803     if (clickType == Press) ErrorPopDown();
6804
6805     x = EventToSquare(xPix, BOARD_WIDTH);
6806     y = EventToSquare(yPix, BOARD_HEIGHT);
6807     if (!flipView && y >= 0) {
6808         y = BOARD_HEIGHT - 1 - y;
6809     }
6810     if (flipView && x >= 0) {
6811         x = BOARD_WIDTH - 1 - x;
6812     }
6813
6814     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6815         defaultPromoChoice = promoSweep;
6816         promoSweep = EmptySquare;   // terminate sweep
6817         promoDefaultAltered = TRUE;
6818         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6819     }
6820
6821     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6822         if(clickType == Release) return; // ignore upclick of click-click destination
6823         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6824         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6825         if(gameInfo.holdingsWidth &&
6826                 (WhiteOnMove(currentMove)
6827                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6828                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6829             // click in right holdings, for determining promotion piece
6830             ChessSquare p = boards[currentMove][y][x];
6831             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6832             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6833             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6834                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6835                 fromX = fromY = -1;
6836                 return;
6837             }
6838         }
6839         DrawPosition(FALSE, boards[currentMove]);
6840         return;
6841     }
6842
6843     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6844     if(clickType == Press
6845             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6846               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6847               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6848         return;
6849
6850     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6851         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6852
6853     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6854         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6855                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6856         defaultPromoChoice = DefaultPromoChoice(side);
6857     }
6858
6859     autoQueen = appData.alwaysPromoteToQueen;
6860
6861     if (fromX == -1) {
6862       int originalY = y;
6863       gatingPiece = EmptySquare;
6864       if (clickType != Press) {
6865         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6866             DragPieceEnd(xPix, yPix); dragging = 0;
6867             DrawPosition(FALSE, NULL);
6868         }
6869         return;
6870       }
6871       fromX = x; fromY = y; toX = toY = -1;
6872       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6873          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6874          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6875             /* First square */
6876             if (OKToStartUserMove(fromX, fromY)) {
6877                 second = 0;
6878                 MarkTargetSquares(0);
6879                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6880                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6881                     promoSweep = defaultPromoChoice;
6882                     selectFlag = 0; lastX = xPix; lastY = yPix;
6883                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6884                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6885                 }
6886                 if (appData.highlightDragging) {
6887                     SetHighlights(fromX, fromY, -1, -1);
6888                 }
6889             } else fromX = fromY = -1;
6890             return;
6891         }
6892     }
6893
6894     /* fromX != -1 */
6895     if (clickType == Press && gameMode != EditPosition) {
6896         ChessSquare fromP;
6897         ChessSquare toP;
6898         int frc;
6899
6900         // ignore off-board to clicks
6901         if(y < 0 || x < 0) return;
6902
6903         /* Check if clicking again on the same color piece */
6904         fromP = boards[currentMove][fromY][fromX];
6905         toP = boards[currentMove][y][x];
6906         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6907         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6908              WhitePawn <= toP && toP <= WhiteKing &&
6909              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6910              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6911             (BlackPawn <= fromP && fromP <= BlackKing &&
6912              BlackPawn <= toP && toP <= BlackKing &&
6913              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6914              !(fromP == BlackKing && toP == BlackRook && frc))) {
6915             /* Clicked again on same color piece -- changed his mind */
6916             second = (x == fromX && y == fromY);
6917             promoDefaultAltered = FALSE;
6918             MarkTargetSquares(1);
6919            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6920             if (appData.highlightDragging) {
6921                 SetHighlights(x, y, -1, -1);
6922             } else {
6923                 ClearHighlights();
6924             }
6925             if (OKToStartUserMove(x, y)) {
6926                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6927                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6928                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6929                  gatingPiece = boards[currentMove][fromY][fromX];
6930                 else gatingPiece = EmptySquare;
6931                 fromX = x;
6932                 fromY = y; dragging = 1;
6933                 MarkTargetSquares(0);
6934                 DragPieceBegin(xPix, yPix, FALSE);
6935                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6936                     promoSweep = defaultPromoChoice;
6937                     selectFlag = 0; lastX = xPix; lastY = yPix;
6938                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6939                 }
6940             }
6941            }
6942            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6943            second = FALSE; 
6944         }
6945         // ignore clicks on holdings
6946         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6947     }
6948
6949     if (clickType == Release && x == fromX && y == fromY) {
6950         DragPieceEnd(xPix, yPix); dragging = 0;
6951         if(clearFlag) {
6952             // a deferred attempt to click-click move an empty square on top of a piece
6953             boards[currentMove][y][x] = EmptySquare;
6954             ClearHighlights();
6955             DrawPosition(FALSE, boards[currentMove]);
6956             fromX = fromY = -1; clearFlag = 0;
6957             return;
6958         }
6959         if (appData.animateDragging) {
6960             /* Undo animation damage if any */
6961             DrawPosition(FALSE, NULL);
6962         }
6963         if (second) {
6964             /* Second up/down in same square; just abort move */
6965             second = 0;
6966             fromX = fromY = -1;
6967             gatingPiece = EmptySquare;
6968             ClearHighlights();
6969             gotPremove = 0;
6970             ClearPremoveHighlights();
6971         } else {
6972             /* First upclick in same square; start click-click mode */
6973             SetHighlights(x, y, -1, -1);
6974         }
6975         return;
6976     }
6977
6978     clearFlag = 0;
6979
6980     /* we now have a different from- and (possibly off-board) to-square */
6981     /* Completed move */
6982     toX = x;
6983     toY = y;
6984     saveAnimate = appData.animate;
6985     MarkTargetSquares(1);
6986     if (clickType == Press) {
6987         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6988             // must be Edit Position mode with empty-square selected
6989             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
6990             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6991             return;
6992         }
6993         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
6994             ChessSquare piece = boards[currentMove][fromY][fromX];
6995             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
6996             promoSweep = defaultPromoChoice;
6997             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
6998             selectFlag = 0; lastX = xPix; lastY = yPix;
6999             Sweep(0); // Pawn that is going to promote: preview promotion piece
7000             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7001             DrawPosition(FALSE, boards[currentMove]);
7002             return;
7003         }
7004         /* Finish clickclick move */
7005         if (appData.animate || appData.highlightLastMove) {
7006             SetHighlights(fromX, fromY, toX, toY);
7007         } else {
7008             ClearHighlights();
7009         }
7010     } else {
7011         /* Finish drag move */
7012         if (appData.highlightLastMove) {
7013             SetHighlights(fromX, fromY, toX, toY);
7014         } else {
7015             ClearHighlights();
7016         }
7017         DragPieceEnd(xPix, yPix); dragging = 0;
7018         /* Don't animate move and drag both */
7019         appData.animate = FALSE;
7020     }
7021
7022     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7023     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7024         ChessSquare piece = boards[currentMove][fromY][fromX];
7025         if(gameMode == EditPosition && piece != EmptySquare &&
7026            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7027             int n;
7028
7029             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7030                 n = PieceToNumber(piece - (int)BlackPawn);
7031                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7032                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7033                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7034             } else
7035             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7036                 n = PieceToNumber(piece);
7037                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7038                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7039                 boards[currentMove][n][BOARD_WIDTH-2]++;
7040             }
7041             boards[currentMove][fromY][fromX] = EmptySquare;
7042         }
7043         ClearHighlights();
7044         fromX = fromY = -1;
7045         DrawPosition(TRUE, boards[currentMove]);
7046         return;
7047     }
7048
7049     // off-board moves should not be highlighted
7050     if(x < 0 || y < 0) ClearHighlights();
7051
7052     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7053
7054     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7055         SetHighlights(fromX, fromY, toX, toY);
7056         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7057             // [HGM] super: promotion to captured piece selected from holdings
7058             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7059             promotionChoice = TRUE;
7060             // kludge follows to temporarily execute move on display, without promoting yet
7061             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7062             boards[currentMove][toY][toX] = p;
7063             DrawPosition(FALSE, boards[currentMove]);
7064             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7065             boards[currentMove][toY][toX] = q;
7066             DisplayMessage("Click in holdings to choose piece", "");
7067             return;
7068         }
7069         PromotionPopUp();
7070     } else {
7071         int oldMove = currentMove;
7072         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7073         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7074         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7075         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7076            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7077             DrawPosition(TRUE, boards[currentMove]);
7078         fromX = fromY = -1;
7079     }
7080     appData.animate = saveAnimate;
7081     if (appData.animate || appData.animateDragging) {
7082         /* Undo animation damage if needed */
7083         DrawPosition(FALSE, NULL);
7084     }
7085 }
7086
7087 int
7088 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7089 {   // front-end-free part taken out of PieceMenuPopup
7090     int whichMenu; int xSqr, ySqr;
7091
7092     if(seekGraphUp) { // [HGM] seekgraph
7093         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7094         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7095         return -2;
7096     }
7097
7098     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7099          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7100         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7101         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7102         if(action == Press)   {
7103             originalFlip = flipView;
7104             flipView = !flipView; // temporarily flip board to see game from partners perspective
7105             DrawPosition(TRUE, partnerBoard);
7106             DisplayMessage(partnerStatus, "");
7107             partnerUp = TRUE;
7108         } else if(action == Release) {
7109             flipView = originalFlip;
7110             DrawPosition(TRUE, boards[currentMove]);
7111             partnerUp = FALSE;
7112         }
7113         return -2;
7114     }
7115
7116     xSqr = EventToSquare(x, BOARD_WIDTH);
7117     ySqr = EventToSquare(y, BOARD_HEIGHT);
7118     if (action == Release) {
7119         if(pieceSweep != EmptySquare) {
7120             EditPositionMenuEvent(pieceSweep, toX, toY);
7121             pieceSweep = EmptySquare;
7122         } else UnLoadPV(); // [HGM] pv
7123     }
7124     if (action != Press) return -2; // return code to be ignored
7125     switch (gameMode) {
7126       case IcsExamining:
7127         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7128       case EditPosition:
7129         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7130         if (xSqr < 0 || ySqr < 0) return -1;
7131         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7132         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7133         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7134         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7135         NextPiece(0);
7136         return 2; // grab
7137       case IcsObserving:
7138         if(!appData.icsEngineAnalyze) return -1;
7139       case IcsPlayingWhite:
7140       case IcsPlayingBlack:
7141         if(!appData.zippyPlay) goto noZip;
7142       case AnalyzeMode:
7143       case AnalyzeFile:
7144       case MachinePlaysWhite:
7145       case MachinePlaysBlack:
7146       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7147         if (!appData.dropMenu) {
7148           LoadPV(x, y);
7149           return 2; // flag front-end to grab mouse events
7150         }
7151         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7152            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7153       case EditGame:
7154       noZip:
7155         if (xSqr < 0 || ySqr < 0) return -1;
7156         if (!appData.dropMenu || appData.testLegality &&
7157             gameInfo.variant != VariantBughouse &&
7158             gameInfo.variant != VariantCrazyhouse) return -1;
7159         whichMenu = 1; // drop menu
7160         break;
7161       default:
7162         return -1;
7163     }
7164
7165     if (((*fromX = xSqr) < 0) ||
7166         ((*fromY = ySqr) < 0)) {
7167         *fromX = *fromY = -1;
7168         return -1;
7169     }
7170     if (flipView)
7171       *fromX = BOARD_WIDTH - 1 - *fromX;
7172     else
7173       *fromY = BOARD_HEIGHT - 1 - *fromY;
7174
7175     return whichMenu;
7176 }
7177
7178 void
7179 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7180 {
7181 //    char * hint = lastHint;
7182     FrontEndProgramStats stats;
7183
7184     stats.which = cps == &first ? 0 : 1;
7185     stats.depth = cpstats->depth;
7186     stats.nodes = cpstats->nodes;
7187     stats.score = cpstats->score;
7188     stats.time = cpstats->time;
7189     stats.pv = cpstats->movelist;
7190     stats.hint = lastHint;
7191     stats.an_move_index = 0;
7192     stats.an_move_count = 0;
7193
7194     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7195         stats.hint = cpstats->move_name;
7196         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7197         stats.an_move_count = cpstats->nr_moves;
7198     }
7199
7200     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
7201
7202     SetProgramStats( &stats );
7203 }
7204
7205 void
7206 ClearEngineOutputPane (int which)
7207 {
7208     static FrontEndProgramStats dummyStats;
7209     dummyStats.which = which;
7210     dummyStats.pv = "#";
7211     SetProgramStats( &dummyStats );
7212 }
7213
7214 #define MAXPLAYERS 500
7215
7216 char *
7217 TourneyStandings (int display)
7218 {
7219     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7220     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7221     char result, *p, *names[MAXPLAYERS];
7222
7223     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7224         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7225     names[0] = p = strdup(appData.participants);
7226     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7227
7228     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7229
7230     while(result = appData.results[nr]) {
7231         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7232         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7233         wScore = bScore = 0;
7234         switch(result) {
7235           case '+': wScore = 2; break;
7236           case '-': bScore = 2; break;
7237           case '=': wScore = bScore = 1; break;
7238           case ' ':
7239           case '*': return strdup("busy"); // tourney not finished
7240         }
7241         score[w] += wScore;
7242         score[b] += bScore;
7243         games[w]++;
7244         games[b]++;
7245         nr++;
7246     }
7247     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7248     for(w=0; w<nPlayers; w++) {
7249         bScore = -1;
7250         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7251         ranking[w] = b; points[w] = bScore; score[b] = -2;
7252     }
7253     p = malloc(nPlayers*34+1);
7254     for(w=0; w<nPlayers && w<display; w++)
7255         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7256     free(names[0]);
7257     return p;
7258 }
7259
7260 void
7261 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7262 {       // count all piece types
7263         int p, f, r;
7264         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7265         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7266         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7267                 p = board[r][f];
7268                 pCnt[p]++;
7269                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7270                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7271                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7272                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7273                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7274                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7275         }
7276 }
7277
7278 int
7279 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7280 {
7281         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7282         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7283
7284         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7285         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7286         if(myPawns == 2 && nMine == 3) // KPP
7287             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7288         if(myPawns == 1 && nMine == 2) // KP
7289             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7290         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7291             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7292         if(myPawns) return FALSE;
7293         if(pCnt[WhiteRook+side])
7294             return pCnt[BlackRook-side] ||
7295                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7296                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7297                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7298         if(pCnt[WhiteCannon+side]) {
7299             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7300             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7301         }
7302         if(pCnt[WhiteKnight+side])
7303             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7304         return FALSE;
7305 }
7306
7307 int
7308 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7309 {
7310         VariantClass v = gameInfo.variant;
7311
7312         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7313         if(v == VariantShatranj) return TRUE; // always winnable through baring
7314         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7315         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7316
7317         if(v == VariantXiangqi) {
7318                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7319
7320                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7321                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7322                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7323                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7324                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7325                 if(stale) // we have at least one last-rank P plus perhaps C
7326                     return majors // KPKX
7327                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7328                 else // KCA*E*
7329                     return pCnt[WhiteFerz+side] // KCAK
7330                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7331                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7332                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7333
7334         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7335                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7336
7337                 if(nMine == 1) return FALSE; // bare King
7338                 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
7339                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7340                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7341                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7342                 if(pCnt[WhiteKnight+side])
7343                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7344                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7345                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7346                 if(nBishops)
7347                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7348                 if(pCnt[WhiteAlfil+side])
7349                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7350                 if(pCnt[WhiteWazir+side])
7351                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7352         }
7353
7354         return TRUE;
7355 }
7356
7357 int
7358 CompareWithRights (Board b1, Board b2)
7359 {
7360     int rights = 0;
7361     if(!CompareBoards(b1, b2)) return FALSE;
7362     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7363     /* compare castling rights */
7364     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7365            rights++; /* King lost rights, while rook still had them */
7366     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7367         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7368            rights++; /* but at least one rook lost them */
7369     }
7370     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7371            rights++;
7372     if( b1[CASTLING][5] != NoRights ) {
7373         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7374            rights++;
7375     }
7376     return rights == 0;
7377 }
7378
7379 int
7380 Adjudicate (ChessProgramState *cps)
7381 {       // [HGM] some adjudications useful with buggy engines
7382         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7383         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7384         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7385         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7386         int k, count = 0; static int bare = 1;
7387         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7388         Boolean canAdjudicate = !appData.icsActive;
7389
7390         // most tests only when we understand the game, i.e. legality-checking on
7391             if( appData.testLegality )
7392             {   /* [HGM] Some more adjudications for obstinate engines */
7393                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7394                 static int moveCount = 6;
7395                 ChessMove result;
7396                 char *reason = NULL;
7397
7398                 /* Count what is on board. */
7399                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7400
7401                 /* Some material-based adjudications that have to be made before stalemate test */
7402                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7403                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7404                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7405                      if(canAdjudicate && appData.checkMates) {
7406                          if(engineOpponent)
7407                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7408                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7409                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7410                          return 1;
7411                      }
7412                 }
7413
7414                 /* Bare King in Shatranj (loses) or Losers (wins) */
7415                 if( nrW == 1 || nrB == 1) {
7416                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7417                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7418                      if(canAdjudicate && appData.checkMates) {
7419                          if(engineOpponent)
7420                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7421                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7422                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7423                          return 1;
7424                      }
7425                   } else
7426                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7427                   {    /* bare King */
7428                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7429                         if(canAdjudicate && appData.checkMates) {
7430                             /* but only adjudicate if adjudication enabled */
7431                             if(engineOpponent)
7432                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7433                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7434                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7435                             return 1;
7436                         }
7437                   }
7438                 } else bare = 1;
7439
7440
7441             // don't wait for engine to announce game end if we can judge ourselves
7442             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7443               case MT_CHECK:
7444                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7445                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7446                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7447                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7448                             checkCnt++;
7449                         if(checkCnt >= 2) {
7450                             reason = "Xboard adjudication: 3rd check";
7451                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7452                             break;
7453                         }
7454                     }
7455                 }
7456               case MT_NONE:
7457               default:
7458                 break;
7459               case MT_STALEMATE:
7460               case MT_STAINMATE:
7461                 reason = "Xboard adjudication: Stalemate";
7462                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7463                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7464                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7465                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7466                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7467                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7468                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7469                                                                         EP_CHECKMATE : EP_WINS);
7470                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7471                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7472                 }
7473                 break;
7474               case MT_CHECKMATE:
7475                 reason = "Xboard adjudication: Checkmate";
7476                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7477                 break;
7478             }
7479
7480                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7481                     case EP_STALEMATE:
7482                         result = GameIsDrawn; break;
7483                     case EP_CHECKMATE:
7484                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7485                     case EP_WINS:
7486                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7487                     default:
7488                         result = EndOfFile;
7489                 }
7490                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7491                     if(engineOpponent)
7492                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7493                     GameEnds( result, reason, GE_XBOARD );
7494                     return 1;
7495                 }
7496
7497                 /* Next absolutely insufficient mating material. */
7498                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7499                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7500                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7501
7502                      /* always flag draws, for judging claims */
7503                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7504
7505                      if(canAdjudicate && appData.materialDraws) {
7506                          /* but only adjudicate them if adjudication enabled */
7507                          if(engineOpponent) {
7508                            SendToProgram("force\n", engineOpponent); // suppress reply
7509                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7510                          }
7511                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7512                          return 1;
7513                      }
7514                 }
7515
7516                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7517                 if(gameInfo.variant == VariantXiangqi ?
7518                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7519                  : nrW + nrB == 4 &&
7520                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7521                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7522                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7523                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7524                    ) ) {
7525                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7526                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7527                           if(engineOpponent) {
7528                             SendToProgram("force\n", engineOpponent); // suppress reply
7529                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7530                           }
7531                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7532                           return 1;
7533                      }
7534                 } else moveCount = 6;
7535             }
7536         if (appData.debugMode) { int i;
7537             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7538                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7539                     appData.drawRepeats);
7540             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7541               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7542
7543         }
7544
7545         // Repetition draws and 50-move rule can be applied independently of legality testing
7546
7547                 /* Check for rep-draws */
7548                 count = 0;
7549                 for(k = forwardMostMove-2;
7550                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7551                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7552                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7553                     k-=2)
7554                 {   int rights=0;
7555                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7556                         /* compare castling rights */
7557                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7558                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7559                                 rights++; /* King lost rights, while rook still had them */
7560                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7561                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7562                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7563                                    rights++; /* but at least one rook lost them */
7564                         }
7565                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7566                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7567                                 rights++;
7568                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7569                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7570                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7571                                    rights++;
7572                         }
7573                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7574                             && appData.drawRepeats > 1) {
7575                              /* adjudicate after user-specified nr of repeats */
7576                              int result = GameIsDrawn;
7577                              char *details = "XBoard adjudication: repetition draw";
7578                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7579                                 // [HGM] xiangqi: check for forbidden perpetuals
7580                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7581                                 for(m=forwardMostMove; m>k; m-=2) {
7582                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7583                                         ourPerpetual = 0; // the current mover did not always check
7584                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7585                                         hisPerpetual = 0; // the opponent did not always check
7586                                 }
7587                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7588                                                                         ourPerpetual, hisPerpetual);
7589                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7590                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7591                                     details = "Xboard adjudication: perpetual checking";
7592                                 } else
7593                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7594                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7595                                 } else
7596                                 // Now check for perpetual chases
7597                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7598                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7599                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7600                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7601                                         static char resdet[MSG_SIZ];
7602                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7603                                         details = resdet;
7604                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7605                                     } else
7606                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7607                                         break; // Abort repetition-checking loop.
7608                                 }
7609                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7610                              }
7611                              if(engineOpponent) {
7612                                SendToProgram("force\n", engineOpponent); // suppress reply
7613                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7614                              }
7615                              GameEnds( result, details, GE_XBOARD );
7616                              return 1;
7617                         }
7618                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7619                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7620                     }
7621                 }
7622
7623                 /* Now we test for 50-move draws. Determine ply count */
7624                 count = forwardMostMove;
7625                 /* look for last irreversble move */
7626                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7627                     count--;
7628                 /* if we hit starting position, add initial plies */
7629                 if( count == backwardMostMove )
7630                     count -= initialRulePlies;
7631                 count = forwardMostMove - count;
7632                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7633                         // adjust reversible move counter for checks in Xiangqi
7634                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7635                         if(i < backwardMostMove) i = backwardMostMove;
7636                         while(i <= forwardMostMove) {
7637                                 lastCheck = inCheck; // check evasion does not count
7638                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7639                                 if(inCheck || lastCheck) count--; // check does not count
7640                                 i++;
7641                         }
7642                 }
7643                 if( count >= 100)
7644                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7645                          /* this is used to judge if draw claims are legal */
7646                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7647                          if(engineOpponent) {
7648                            SendToProgram("force\n", engineOpponent); // suppress reply
7649                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7650                          }
7651                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7652                          return 1;
7653                 }
7654
7655                 /* if draw offer is pending, treat it as a draw claim
7656                  * when draw condition present, to allow engines a way to
7657                  * claim draws before making their move to avoid a race
7658                  * condition occurring after their move
7659                  */
7660                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7661                          char *p = NULL;
7662                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7663                              p = "Draw claim: 50-move rule";
7664                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7665                              p = "Draw claim: 3-fold repetition";
7666                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7667                              p = "Draw claim: insufficient mating material";
7668                          if( p != NULL && canAdjudicate) {
7669                              if(engineOpponent) {
7670                                SendToProgram("force\n", engineOpponent); // suppress reply
7671                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7672                              }
7673                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7674                              return 1;
7675                          }
7676                 }
7677
7678                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7679                     if(engineOpponent) {
7680                       SendToProgram("force\n", engineOpponent); // suppress reply
7681                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7682                     }
7683                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7684                     return 1;
7685                 }
7686         return 0;
7687 }
7688
7689 char *
7690 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7691 {   // [HGM] book: this routine intercepts moves to simulate book replies
7692     char *bookHit = NULL;
7693
7694     //first determine if the incoming move brings opponent into his book
7695     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7696         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7697     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7698     if(bookHit != NULL && !cps->bookSuspend) {
7699         // make sure opponent is not going to reply after receiving move to book position
7700         SendToProgram("force\n", cps);
7701         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7702     }
7703     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7704     // now arrange restart after book miss
7705     if(bookHit) {
7706         // after a book hit we never send 'go', and the code after the call to this routine
7707         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7708         char buf[MSG_SIZ], *move = bookHit;
7709         if(cps->useSAN) {
7710             int fromX, fromY, toX, toY;
7711             char promoChar;
7712             ChessMove moveType;
7713             move = buf + 30;
7714             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7715                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7716                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7717                                     PosFlags(forwardMostMove),
7718                                     fromY, fromX, toY, toX, promoChar, move);
7719             } else {
7720                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7721                 bookHit = NULL;
7722             }
7723         }
7724         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7725         SendToProgram(buf, cps);
7726         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7727     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7728         SendToProgram("go\n", cps);
7729         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7730     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7731         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7732             SendToProgram("go\n", cps);
7733         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7734     }
7735     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7736 }
7737
7738 char *savedMessage;
7739 ChessProgramState *savedState;
7740 void
7741 DeferredBookMove (void)
7742 {
7743         if(savedState->lastPing != savedState->lastPong)
7744                     ScheduleDelayedEvent(DeferredBookMove, 10);
7745         else
7746         HandleMachineMove(savedMessage, savedState);
7747 }
7748
7749 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7750
7751 void
7752 HandleMachineMove (char *message, ChessProgramState *cps)
7753 {
7754     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7755     char realname[MSG_SIZ];
7756     int fromX, fromY, toX, toY;
7757     ChessMove moveType;
7758     char promoChar;
7759     char *p, *pv=buf1;
7760     int machineWhite;
7761     char *bookHit;
7762
7763     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7764         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7765         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7766             DisplayError(_("Invalid pairing from pairing engine"), 0);
7767             return;
7768         }
7769         pairingReceived = 1;
7770         NextMatchGame();
7771         return; // Skim the pairing messages here.
7772     }
7773
7774     cps->userError = 0;
7775
7776 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7777     /*
7778      * Kludge to ignore BEL characters
7779      */
7780     while (*message == '\007') message++;
7781
7782     /*
7783      * [HGM] engine debug message: ignore lines starting with '#' character
7784      */
7785     if(cps->debug && *message == '#') return;
7786
7787     /*
7788      * Look for book output
7789      */
7790     if (cps == &first && bookRequested) {
7791         if (message[0] == '\t' || message[0] == ' ') {
7792             /* Part of the book output is here; append it */
7793             strcat(bookOutput, message);
7794             strcat(bookOutput, "  \n");
7795             return;
7796         } else if (bookOutput[0] != NULLCHAR) {
7797             /* All of book output has arrived; display it */
7798             char *p = bookOutput;
7799             while (*p != NULLCHAR) {
7800                 if (*p == '\t') *p = ' ';
7801                 p++;
7802             }
7803             DisplayInformation(bookOutput);
7804             bookRequested = FALSE;
7805             /* Fall through to parse the current output */
7806         }
7807     }
7808
7809     /*
7810      * Look for machine move.
7811      */
7812     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7813         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7814     {
7815         /* This method is only useful on engines that support ping */
7816         if (cps->lastPing != cps->lastPong) {
7817           if (gameMode == BeginningOfGame) {
7818             /* Extra move from before last new; ignore */
7819             if (appData.debugMode) {
7820                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7821             }
7822           } else {
7823             if (appData.debugMode) {
7824                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7825                         cps->which, gameMode);
7826             }
7827
7828             SendToProgram("undo\n", cps);
7829           }
7830           return;
7831         }
7832
7833         switch (gameMode) {
7834           case BeginningOfGame:
7835             /* Extra move from before last reset; ignore */
7836             if (appData.debugMode) {
7837                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7838             }
7839             return;
7840
7841           case EndOfGame:
7842           case IcsIdle:
7843           default:
7844             /* Extra move after we tried to stop.  The mode test is
7845                not a reliable way of detecting this problem, but it's
7846                the best we can do on engines that don't support ping.
7847             */
7848             if (appData.debugMode) {
7849                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7850                         cps->which, gameMode);
7851             }
7852             SendToProgram("undo\n", cps);
7853             return;
7854
7855           case MachinePlaysWhite:
7856           case IcsPlayingWhite:
7857             machineWhite = TRUE;
7858             break;
7859
7860           case MachinePlaysBlack:
7861           case IcsPlayingBlack:
7862             machineWhite = FALSE;
7863             break;
7864
7865           case TwoMachinesPlay:
7866             machineWhite = (cps->twoMachinesColor[0] == 'w');
7867             break;
7868         }
7869         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7870             if (appData.debugMode) {
7871                 fprintf(debugFP,
7872                         "Ignoring move out of turn by %s, gameMode %d"
7873                         ", forwardMost %d\n",
7874                         cps->which, gameMode, forwardMostMove);
7875             }
7876             return;
7877         }
7878
7879     if (appData.debugMode) { int f = forwardMostMove;
7880         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7881                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7882                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7883     }
7884         if(cps->alphaRank) AlphaRank(machineMove, 4);
7885         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7886                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7887             /* Machine move could not be parsed; ignore it. */
7888           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7889                     machineMove, _(cps->which));
7890             DisplayError(buf1, 0);
7891             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7892                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7893             if (gameMode == TwoMachinesPlay) {
7894               GameEnds(machineWhite ? BlackWins : WhiteWins,
7895                        buf1, GE_XBOARD);
7896             }
7897             return;
7898         }
7899
7900         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7901         /* So we have to redo legality test with true e.p. status here,  */
7902         /* to make sure an illegal e.p. capture does not slip through,   */
7903         /* to cause a forfeit on a justified illegal-move complaint      */
7904         /* of the opponent.                                              */
7905         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7906            ChessMove moveType;
7907            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7908                              fromY, fromX, toY, toX, promoChar);
7909             if (appData.debugMode) {
7910                 int i;
7911                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7912                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7913                 fprintf(debugFP, "castling rights\n");
7914             }
7915             if(moveType == IllegalMove) {
7916               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7917                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7918                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7919                            buf1, GE_XBOARD);
7920                 return;
7921            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7922            /* [HGM] Kludge to handle engines that send FRC-style castling
7923               when they shouldn't (like TSCP-Gothic) */
7924            switch(moveType) {
7925              case WhiteASideCastleFR:
7926              case BlackASideCastleFR:
7927                toX+=2;
7928                currentMoveString[2]++;
7929                break;
7930              case WhiteHSideCastleFR:
7931              case BlackHSideCastleFR:
7932                toX--;
7933                currentMoveString[2]--;
7934                break;
7935              default: ; // nothing to do, but suppresses warning of pedantic compilers
7936            }
7937         }
7938         hintRequested = FALSE;
7939         lastHint[0] = NULLCHAR;
7940         bookRequested = FALSE;
7941         /* Program may be pondering now */
7942         cps->maybeThinking = TRUE;
7943         if (cps->sendTime == 2) cps->sendTime = 1;
7944         if (cps->offeredDraw) cps->offeredDraw--;
7945
7946         /* [AS] Save move info*/
7947         pvInfoList[ forwardMostMove ].score = programStats.score;
7948         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7949         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7950
7951         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7952
7953         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7954         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7955             int count = 0;
7956
7957             while( count < adjudicateLossPlies ) {
7958                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7959
7960                 if( count & 1 ) {
7961                     score = -score; /* Flip score for winning side */
7962                 }
7963
7964                 if( score > adjudicateLossThreshold ) {
7965                     break;
7966                 }
7967
7968                 count++;
7969             }
7970
7971             if( count >= adjudicateLossPlies ) {
7972                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7973
7974                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7975                     "Xboard adjudication",
7976                     GE_XBOARD );
7977
7978                 return;
7979             }
7980         }
7981
7982         if(Adjudicate(cps)) {
7983             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7984             return; // [HGM] adjudicate: for all automatic game ends
7985         }
7986
7987 #if ZIPPY
7988         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7989             first.initDone) {
7990           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7991                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7992                 SendToICS("draw ");
7993                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7994           }
7995           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7996           ics_user_moved = 1;
7997           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7998                 char buf[3*MSG_SIZ];
7999
8000                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8001                         programStats.score / 100.,
8002                         programStats.depth,
8003                         programStats.time / 100.,
8004                         (unsigned int)programStats.nodes,
8005                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8006                         programStats.movelist);
8007                 SendToICS(buf);
8008 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8009           }
8010         }
8011 #endif
8012
8013         /* [AS] Clear stats for next move */
8014         ClearProgramStats();
8015         thinkOutput[0] = NULLCHAR;
8016         hiddenThinkOutputState = 0;
8017
8018         bookHit = NULL;
8019         if (gameMode == TwoMachinesPlay) {
8020             /* [HGM] relaying draw offers moved to after reception of move */
8021             /* and interpreting offer as claim if it brings draw condition */
8022             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8023                 SendToProgram("draw\n", cps->other);
8024             }
8025             if (cps->other->sendTime) {
8026                 SendTimeRemaining(cps->other,
8027                                   cps->other->twoMachinesColor[0] == 'w');
8028             }
8029             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8030             if (firstMove && !bookHit) {
8031                 firstMove = FALSE;
8032                 if (cps->other->useColors) {
8033                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8034                 }
8035                 SendToProgram("go\n", cps->other);
8036             }
8037             cps->other->maybeThinking = TRUE;
8038         }
8039
8040         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8041
8042         if (!pausing && appData.ringBellAfterMoves) {
8043             RingBell();
8044         }
8045
8046         /*
8047          * Reenable menu items that were disabled while
8048          * machine was thinking
8049          */
8050         if (gameMode != TwoMachinesPlay)
8051             SetUserThinkingEnables();
8052
8053         // [HGM] book: after book hit opponent has received move and is now in force mode
8054         // force the book reply into it, and then fake that it outputted this move by jumping
8055         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8056         if(bookHit) {
8057                 static char bookMove[MSG_SIZ]; // a bit generous?
8058
8059                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8060                 strcat(bookMove, bookHit);
8061                 message = bookMove;
8062                 cps = cps->other;
8063                 programStats.nodes = programStats.depth = programStats.time =
8064                 programStats.score = programStats.got_only_move = 0;
8065                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8066
8067                 if(cps->lastPing != cps->lastPong) {
8068                     savedMessage = message; // args for deferred call
8069                     savedState = cps;
8070                     ScheduleDelayedEvent(DeferredBookMove, 10);
8071                     return;
8072                 }
8073                 goto FakeBookMove;
8074         }
8075
8076         return;
8077     }
8078
8079     /* Set special modes for chess engines.  Later something general
8080      *  could be added here; for now there is just one kludge feature,
8081      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8082      *  when "xboard" is given as an interactive command.
8083      */
8084     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8085         cps->useSigint = FALSE;
8086         cps->useSigterm = FALSE;
8087     }
8088     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8089       ParseFeatures(message+8, cps);
8090       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8091     }
8092
8093     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8094                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8095       int dummy, s=6; char buf[MSG_SIZ];
8096       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8097       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8098       if(startedFromSetupPosition) return;
8099       ParseFEN(boards[0], &dummy, message+s);
8100       DrawPosition(TRUE, boards[0]);
8101       startedFromSetupPosition = TRUE;
8102       return;
8103     }
8104     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8105      * want this, I was asked to put it in, and obliged.
8106      */
8107     if (!strncmp(message, "setboard ", 9)) {
8108         Board initial_position;
8109
8110         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8111
8112         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8113             DisplayError(_("Bad FEN received from engine"), 0);
8114             return ;
8115         } else {
8116            Reset(TRUE, FALSE);
8117            CopyBoard(boards[0], initial_position);
8118            initialRulePlies = FENrulePlies;
8119            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8120            else gameMode = MachinePlaysBlack;
8121            DrawPosition(FALSE, boards[currentMove]);
8122         }
8123         return;
8124     }
8125
8126     /*
8127      * Look for communication commands
8128      */
8129     if (!strncmp(message, "telluser ", 9)) {
8130         if(message[9] == '\\' && message[10] == '\\')
8131             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8132         PlayTellSound();
8133         DisplayNote(message + 9);
8134         return;
8135     }
8136     if (!strncmp(message, "tellusererror ", 14)) {
8137         cps->userError = 1;
8138         if(message[14] == '\\' && message[15] == '\\')
8139             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8140         PlayTellSound();
8141         DisplayError(message + 14, 0);
8142         return;
8143     }
8144     if (!strncmp(message, "tellopponent ", 13)) {
8145       if (appData.icsActive) {
8146         if (loggedOn) {
8147           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8148           SendToICS(buf1);
8149         }
8150       } else {
8151         DisplayNote(message + 13);
8152       }
8153       return;
8154     }
8155     if (!strncmp(message, "tellothers ", 11)) {
8156       if (appData.icsActive) {
8157         if (loggedOn) {
8158           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8159           SendToICS(buf1);
8160         }
8161       }
8162       return;
8163     }
8164     if (!strncmp(message, "tellall ", 8)) {
8165       if (appData.icsActive) {
8166         if (loggedOn) {
8167           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8168           SendToICS(buf1);
8169         }
8170       } else {
8171         DisplayNote(message + 8);
8172       }
8173       return;
8174     }
8175     if (strncmp(message, "warning", 7) == 0) {
8176         /* Undocumented feature, use tellusererror in new code */
8177         DisplayError(message, 0);
8178         return;
8179     }
8180     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8181         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8182         strcat(realname, " query");
8183         AskQuestion(realname, buf2, buf1, cps->pr);
8184         return;
8185     }
8186     /* Commands from the engine directly to ICS.  We don't allow these to be
8187      *  sent until we are logged on. Crafty kibitzes have been known to
8188      *  interfere with the login process.
8189      */
8190     if (loggedOn) {
8191         if (!strncmp(message, "tellics ", 8)) {
8192             SendToICS(message + 8);
8193             SendToICS("\n");
8194             return;
8195         }
8196         if (!strncmp(message, "tellicsnoalias ", 15)) {
8197             SendToICS(ics_prefix);
8198             SendToICS(message + 15);
8199             SendToICS("\n");
8200             return;
8201         }
8202         /* The following are for backward compatibility only */
8203         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8204             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8205             SendToICS(ics_prefix);
8206             SendToICS(message);
8207             SendToICS("\n");
8208             return;
8209         }
8210     }
8211     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8212         return;
8213     }
8214     /*
8215      * If the move is illegal, cancel it and redraw the board.
8216      * Also deal with other error cases.  Matching is rather loose
8217      * here to accommodate engines written before the spec.
8218      */
8219     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8220         strncmp(message, "Error", 5) == 0) {
8221         if (StrStr(message, "name") ||
8222             StrStr(message, "rating") || StrStr(message, "?") ||
8223             StrStr(message, "result") || StrStr(message, "board") ||
8224             StrStr(message, "bk") || StrStr(message, "computer") ||
8225             StrStr(message, "variant") || StrStr(message, "hint") ||
8226             StrStr(message, "random") || StrStr(message, "depth") ||
8227             StrStr(message, "accepted")) {
8228             return;
8229         }
8230         if (StrStr(message, "protover")) {
8231           /* Program is responding to input, so it's apparently done
8232              initializing, and this error message indicates it is
8233              protocol version 1.  So we don't need to wait any longer
8234              for it to initialize and send feature commands. */
8235           FeatureDone(cps, 1);
8236           cps->protocolVersion = 1;
8237           return;
8238         }
8239         cps->maybeThinking = FALSE;
8240
8241         if (StrStr(message, "draw")) {
8242             /* Program doesn't have "draw" command */
8243             cps->sendDrawOffers = 0;
8244             return;
8245         }
8246         if (cps->sendTime != 1 &&
8247             (StrStr(message, "time") || StrStr(message, "otim"))) {
8248           /* Program apparently doesn't have "time" or "otim" command */
8249           cps->sendTime = 0;
8250           return;
8251         }
8252         if (StrStr(message, "analyze")) {
8253             cps->analysisSupport = FALSE;
8254             cps->analyzing = FALSE;
8255 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8256             EditGameEvent(); // [HGM] try to preserve loaded game
8257             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8258             DisplayError(buf2, 0);
8259             return;
8260         }
8261         if (StrStr(message, "(no matching move)st")) {
8262           /* Special kludge for GNU Chess 4 only */
8263           cps->stKludge = TRUE;
8264           SendTimeControl(cps, movesPerSession, timeControl,
8265                           timeIncrement, appData.searchDepth,
8266                           searchTime);
8267           return;
8268         }
8269         if (StrStr(message, "(no matching move)sd")) {
8270           /* Special kludge for GNU Chess 4 only */
8271           cps->sdKludge = TRUE;
8272           SendTimeControl(cps, movesPerSession, timeControl,
8273                           timeIncrement, appData.searchDepth,
8274                           searchTime);
8275           return;
8276         }
8277         if (!StrStr(message, "llegal")) {
8278             return;
8279         }
8280         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8281             gameMode == IcsIdle) return;
8282         if (forwardMostMove <= backwardMostMove) return;
8283         if (pausing) PauseEvent();
8284       if(appData.forceIllegal) {
8285             // [HGM] illegal: machine refused move; force position after move into it
8286           SendToProgram("force\n", cps);
8287           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8288                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8289                 // when black is to move, while there might be nothing on a2 or black
8290                 // might already have the move. So send the board as if white has the move.
8291                 // But first we must change the stm of the engine, as it refused the last move
8292                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8293                 if(WhiteOnMove(forwardMostMove)) {
8294                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8295                     SendBoard(cps, forwardMostMove); // kludgeless board
8296                 } else {
8297                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8298                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8299                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8300                 }
8301           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8302             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8303                  gameMode == TwoMachinesPlay)
8304               SendToProgram("go\n", cps);
8305             return;
8306       } else
8307         if (gameMode == PlayFromGameFile) {
8308             /* Stop reading this game file */
8309             gameMode = EditGame;
8310             ModeHighlight();
8311         }
8312         /* [HGM] illegal-move claim should forfeit game when Xboard */
8313         /* only passes fully legal moves                            */
8314         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8315             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8316                                 "False illegal-move claim", GE_XBOARD );
8317             return; // do not take back move we tested as valid
8318         }
8319         currentMove = forwardMostMove-1;
8320         DisplayMove(currentMove-1); /* before DisplayMoveError */
8321         SwitchClocks(forwardMostMove-1); // [HGM] race
8322         DisplayBothClocks();
8323         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8324                 parseList[currentMove], _(cps->which));
8325         DisplayMoveError(buf1);
8326         DrawPosition(FALSE, boards[currentMove]);
8327
8328         SetUserThinkingEnables();
8329         return;
8330     }
8331     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8332         /* Program has a broken "time" command that
8333            outputs a string not ending in newline.
8334            Don't use it. */
8335         cps->sendTime = 0;
8336     }
8337
8338     /*
8339      * If chess program startup fails, exit with an error message.
8340      * Attempts to recover here are futile.
8341      */
8342     if ((StrStr(message, "unknown host") != NULL)
8343         || (StrStr(message, "No remote directory") != NULL)
8344         || (StrStr(message, "not found") != NULL)
8345         || (StrStr(message, "No such file") != NULL)
8346         || (StrStr(message, "can't alloc") != NULL)
8347         || (StrStr(message, "Permission denied") != NULL)) {
8348
8349         cps->maybeThinking = FALSE;
8350         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8351                 _(cps->which), cps->program, cps->host, message);
8352         RemoveInputSource(cps->isr);
8353         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8354             if(cps == &first) appData.noChessProgram = TRUE;
8355             DisplayError(buf1, 0);
8356         }
8357         return;
8358     }
8359
8360     /*
8361      * Look for hint output
8362      */
8363     if (sscanf(message, "Hint: %s", buf1) == 1) {
8364         if (cps == &first && hintRequested) {
8365             hintRequested = FALSE;
8366             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8367                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8368                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8369                                     PosFlags(forwardMostMove),
8370                                     fromY, fromX, toY, toX, promoChar, buf1);
8371                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8372                 DisplayInformation(buf2);
8373             } else {
8374                 /* Hint move could not be parsed!? */
8375               snprintf(buf2, sizeof(buf2),
8376                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8377                         buf1, _(cps->which));
8378                 DisplayError(buf2, 0);
8379             }
8380         } else {
8381           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8382         }
8383         return;
8384     }
8385
8386     /*
8387      * Ignore other messages if game is not in progress
8388      */
8389     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8390         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8391
8392     /*
8393      * look for win, lose, draw, or draw offer
8394      */
8395     if (strncmp(message, "1-0", 3) == 0) {
8396         char *p, *q, *r = "";
8397         p = strchr(message, '{');
8398         if (p) {
8399             q = strchr(p, '}');
8400             if (q) {
8401                 *q = NULLCHAR;
8402                 r = p + 1;
8403             }
8404         }
8405         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8406         return;
8407     } else if (strncmp(message, "0-1", 3) == 0) {
8408         char *p, *q, *r = "";
8409         p = strchr(message, '{');
8410         if (p) {
8411             q = strchr(p, '}');
8412             if (q) {
8413                 *q = NULLCHAR;
8414                 r = p + 1;
8415             }
8416         }
8417         /* Kludge for Arasan 4.1 bug */
8418         if (strcmp(r, "Black resigns") == 0) {
8419             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8420             return;
8421         }
8422         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8423         return;
8424     } else if (strncmp(message, "1/2", 3) == 0) {
8425         char *p, *q, *r = "";
8426         p = strchr(message, '{');
8427         if (p) {
8428             q = strchr(p, '}');
8429             if (q) {
8430                 *q = NULLCHAR;
8431                 r = p + 1;
8432             }
8433         }
8434
8435         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8436         return;
8437
8438     } else if (strncmp(message, "White resign", 12) == 0) {
8439         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8440         return;
8441     } else if (strncmp(message, "Black resign", 12) == 0) {
8442         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8443         return;
8444     } else if (strncmp(message, "White matches", 13) == 0 ||
8445                strncmp(message, "Black matches", 13) == 0   ) {
8446         /* [HGM] ignore GNUShogi noises */
8447         return;
8448     } else if (strncmp(message, "White", 5) == 0 &&
8449                message[5] != '(' &&
8450                StrStr(message, "Black") == NULL) {
8451         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8452         return;
8453     } else if (strncmp(message, "Black", 5) == 0 &&
8454                message[5] != '(') {
8455         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8456         return;
8457     } else if (strcmp(message, "resign") == 0 ||
8458                strcmp(message, "computer resigns") == 0) {
8459         switch (gameMode) {
8460           case MachinePlaysBlack:
8461           case IcsPlayingBlack:
8462             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8463             break;
8464           case MachinePlaysWhite:
8465           case IcsPlayingWhite:
8466             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8467             break;
8468           case TwoMachinesPlay:
8469             if (cps->twoMachinesColor[0] == 'w')
8470               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8471             else
8472               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8473             break;
8474           default:
8475             /* can't happen */
8476             break;
8477         }
8478         return;
8479     } else if (strncmp(message, "opponent mates", 14) == 0) {
8480         switch (gameMode) {
8481           case MachinePlaysBlack:
8482           case IcsPlayingBlack:
8483             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8484             break;
8485           case MachinePlaysWhite:
8486           case IcsPlayingWhite:
8487             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8488             break;
8489           case TwoMachinesPlay:
8490             if (cps->twoMachinesColor[0] == 'w')
8491               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8492             else
8493               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8494             break;
8495           default:
8496             /* can't happen */
8497             break;
8498         }
8499         return;
8500     } else if (strncmp(message, "computer mates", 14) == 0) {
8501         switch (gameMode) {
8502           case MachinePlaysBlack:
8503           case IcsPlayingBlack:
8504             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8505             break;
8506           case MachinePlaysWhite:
8507           case IcsPlayingWhite:
8508             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8509             break;
8510           case TwoMachinesPlay:
8511             if (cps->twoMachinesColor[0] == 'w')
8512               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8513             else
8514               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8515             break;
8516           default:
8517             /* can't happen */
8518             break;
8519         }
8520         return;
8521     } else if (strncmp(message, "checkmate", 9) == 0) {
8522         if (WhiteOnMove(forwardMostMove)) {
8523             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8524         } else {
8525             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8526         }
8527         return;
8528     } else if (strstr(message, "Draw") != NULL ||
8529                strstr(message, "game is a draw") != NULL) {
8530         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8531         return;
8532     } else if (strstr(message, "offer") != NULL &&
8533                strstr(message, "draw") != NULL) {
8534 #if ZIPPY
8535         if (appData.zippyPlay && first.initDone) {
8536             /* Relay offer to ICS */
8537             SendToICS(ics_prefix);
8538             SendToICS("draw\n");
8539         }
8540 #endif
8541         cps->offeredDraw = 2; /* valid until this engine moves twice */
8542         if (gameMode == TwoMachinesPlay) {
8543             if (cps->other->offeredDraw) {
8544                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8545             /* [HGM] in two-machine mode we delay relaying draw offer      */
8546             /* until after we also have move, to see if it is really claim */
8547             }
8548         } else if (gameMode == MachinePlaysWhite ||
8549                    gameMode == MachinePlaysBlack) {
8550           if (userOfferedDraw) {
8551             DisplayInformation(_("Machine accepts your draw offer"));
8552             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8553           } else {
8554             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8555           }
8556         }
8557     }
8558
8559
8560     /*
8561      * Look for thinking output
8562      */
8563     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8564           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8565                                 ) {
8566         int plylev, mvleft, mvtot, curscore, time;
8567         char mvname[MOVE_LEN];
8568         u64 nodes; // [DM]
8569         char plyext;
8570         int ignore = FALSE;
8571         int prefixHint = FALSE;
8572         mvname[0] = NULLCHAR;
8573
8574         switch (gameMode) {
8575           case MachinePlaysBlack:
8576           case IcsPlayingBlack:
8577             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8578             break;
8579           case MachinePlaysWhite:
8580           case IcsPlayingWhite:
8581             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8582             break;
8583           case AnalyzeMode:
8584           case AnalyzeFile:
8585             break;
8586           case IcsObserving: /* [DM] icsEngineAnalyze */
8587             if (!appData.icsEngineAnalyze) ignore = TRUE;
8588             break;
8589           case TwoMachinesPlay:
8590             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8591                 ignore = TRUE;
8592             }
8593             break;
8594           default:
8595             ignore = TRUE;
8596             break;
8597         }
8598
8599         if (!ignore) {
8600             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8601             buf1[0] = NULLCHAR;
8602             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8603                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8604
8605                 if (plyext != ' ' && plyext != '\t') {
8606                     time *= 100;
8607                 }
8608
8609                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8610                 if( cps->scoreIsAbsolute &&
8611                     ( gameMode == MachinePlaysBlack ||
8612                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8613                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8614                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8615                      !WhiteOnMove(currentMove)
8616                     ) )
8617                 {
8618                     curscore = -curscore;
8619                 }
8620
8621                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8622
8623                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8624                         char buf[MSG_SIZ];
8625                         FILE *f;
8626                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8627                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8628                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8629                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8630                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8631                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8632                                 fclose(f);
8633                         } else DisplayError(_("failed writing PV"), 0);
8634                 }
8635
8636                 tempStats.depth = plylev;
8637                 tempStats.nodes = nodes;
8638                 tempStats.time = time;
8639                 tempStats.score = curscore;
8640                 tempStats.got_only_move = 0;
8641
8642                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8643                         int ticklen;
8644
8645                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8646                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8647                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8648                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8649                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8650                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8651                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8652                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8653                 }
8654
8655                 /* Buffer overflow protection */
8656                 if (pv[0] != NULLCHAR) {
8657                     if (strlen(pv) >= sizeof(tempStats.movelist)
8658                         && appData.debugMode) {
8659                         fprintf(debugFP,
8660                                 "PV is too long; using the first %u bytes.\n",
8661                                 (unsigned) sizeof(tempStats.movelist) - 1);
8662                     }
8663
8664                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8665                 } else {
8666                     sprintf(tempStats.movelist, " no PV\n");
8667                 }
8668
8669                 if (tempStats.seen_stat) {
8670                     tempStats.ok_to_send = 1;
8671                 }
8672
8673                 if (strchr(tempStats.movelist, '(') != NULL) {
8674                     tempStats.line_is_book = 1;
8675                     tempStats.nr_moves = 0;
8676                     tempStats.moves_left = 0;
8677                 } else {
8678                     tempStats.line_is_book = 0;
8679                 }
8680
8681                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8682                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8683
8684                 SendProgramStatsToFrontend( cps, &tempStats );
8685
8686                 /*
8687                     [AS] Protect the thinkOutput buffer from overflow... this
8688                     is only useful if buf1 hasn't overflowed first!
8689                 */
8690                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8691                          plylev,
8692                          (gameMode == TwoMachinesPlay ?
8693                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8694                          ((double) curscore) / 100.0,
8695                          prefixHint ? lastHint : "",
8696                          prefixHint ? " " : "" );
8697
8698                 if( buf1[0] != NULLCHAR ) {
8699                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8700
8701                     if( strlen(pv) > max_len ) {
8702                         if( appData.debugMode) {
8703                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8704                         }
8705                         pv[max_len+1] = '\0';
8706                     }
8707
8708                     strcat( thinkOutput, pv);
8709                 }
8710
8711                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8712                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8713                     DisplayMove(currentMove - 1);
8714                 }
8715                 return;
8716
8717             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8718                 /* crafty (9.25+) says "(only move) <move>"
8719                  * if there is only 1 legal move
8720                  */
8721                 sscanf(p, "(only move) %s", buf1);
8722                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8723                 sprintf(programStats.movelist, "%s (only move)", buf1);
8724                 programStats.depth = 1;
8725                 programStats.nr_moves = 1;
8726                 programStats.moves_left = 1;
8727                 programStats.nodes = 1;
8728                 programStats.time = 1;
8729                 programStats.got_only_move = 1;
8730
8731                 /* Not really, but we also use this member to
8732                    mean "line isn't going to change" (Crafty
8733                    isn't searching, so stats won't change) */
8734                 programStats.line_is_book = 1;
8735
8736                 SendProgramStatsToFrontend( cps, &programStats );
8737
8738                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8739                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8740                     DisplayMove(currentMove - 1);
8741                 }
8742                 return;
8743             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8744                               &time, &nodes, &plylev, &mvleft,
8745                               &mvtot, mvname) >= 5) {
8746                 /* The stat01: line is from Crafty (9.29+) in response
8747                    to the "." command */
8748                 programStats.seen_stat = 1;
8749                 cps->maybeThinking = TRUE;
8750
8751                 if (programStats.got_only_move || !appData.periodicUpdates)
8752                   return;
8753
8754                 programStats.depth = plylev;
8755                 programStats.time = time;
8756                 programStats.nodes = nodes;
8757                 programStats.moves_left = mvleft;
8758                 programStats.nr_moves = mvtot;
8759                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8760                 programStats.ok_to_send = 1;
8761                 programStats.movelist[0] = '\0';
8762
8763                 SendProgramStatsToFrontend( cps, &programStats );
8764
8765                 return;
8766
8767             } else if (strncmp(message,"++",2) == 0) {
8768                 /* Crafty 9.29+ outputs this */
8769                 programStats.got_fail = 2;
8770                 return;
8771
8772             } else if (strncmp(message,"--",2) == 0) {
8773                 /* Crafty 9.29+ outputs this */
8774                 programStats.got_fail = 1;
8775                 return;
8776
8777             } else if (thinkOutput[0] != NULLCHAR &&
8778                        strncmp(message, "    ", 4) == 0) {
8779                 unsigned message_len;
8780
8781                 p = message;
8782                 while (*p && *p == ' ') p++;
8783
8784                 message_len = strlen( p );
8785
8786                 /* [AS] Avoid buffer overflow */
8787                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8788                     strcat(thinkOutput, " ");
8789                     strcat(thinkOutput, p);
8790                 }
8791
8792                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8793                     strcat(programStats.movelist, " ");
8794                     strcat(programStats.movelist, p);
8795                 }
8796
8797                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8798                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8799                     DisplayMove(currentMove - 1);
8800                 }
8801                 return;
8802             }
8803         }
8804         else {
8805             buf1[0] = NULLCHAR;
8806
8807             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8808                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8809             {
8810                 ChessProgramStats cpstats;
8811
8812                 if (plyext != ' ' && plyext != '\t') {
8813                     time *= 100;
8814                 }
8815
8816                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8817                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8818                     curscore = -curscore;
8819                 }
8820
8821                 cpstats.depth = plylev;
8822                 cpstats.nodes = nodes;
8823                 cpstats.time = time;
8824                 cpstats.score = curscore;
8825                 cpstats.got_only_move = 0;
8826                 cpstats.movelist[0] = '\0';
8827
8828                 if (buf1[0] != NULLCHAR) {
8829                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8830                 }
8831
8832                 cpstats.ok_to_send = 0;
8833                 cpstats.line_is_book = 0;
8834                 cpstats.nr_moves = 0;
8835                 cpstats.moves_left = 0;
8836
8837                 SendProgramStatsToFrontend( cps, &cpstats );
8838             }
8839         }
8840     }
8841 }
8842
8843
8844 /* Parse a game score from the character string "game", and
8845    record it as the history of the current game.  The game
8846    score is NOT assumed to start from the standard position.
8847    The display is not updated in any way.
8848    */
8849 void
8850 ParseGameHistory (char *game)
8851 {
8852     ChessMove moveType;
8853     int fromX, fromY, toX, toY, boardIndex;
8854     char promoChar;
8855     char *p, *q;
8856     char buf[MSG_SIZ];
8857
8858     if (appData.debugMode)
8859       fprintf(debugFP, "Parsing game history: %s\n", game);
8860
8861     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8862     gameInfo.site = StrSave(appData.icsHost);
8863     gameInfo.date = PGNDate();
8864     gameInfo.round = StrSave("-");
8865
8866     /* Parse out names of players */
8867     while (*game == ' ') game++;
8868     p = buf;
8869     while (*game != ' ') *p++ = *game++;
8870     *p = NULLCHAR;
8871     gameInfo.white = StrSave(buf);
8872     while (*game == ' ') game++;
8873     p = buf;
8874     while (*game != ' ' && *game != '\n') *p++ = *game++;
8875     *p = NULLCHAR;
8876     gameInfo.black = StrSave(buf);
8877
8878     /* Parse moves */
8879     boardIndex = blackPlaysFirst ? 1 : 0;
8880     yynewstr(game);
8881     for (;;) {
8882         yyboardindex = boardIndex;
8883         moveType = (ChessMove) Myylex();
8884         switch (moveType) {
8885           case IllegalMove:             /* maybe suicide chess, etc. */
8886   if (appData.debugMode) {
8887     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8888     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8889     setbuf(debugFP, NULL);
8890   }
8891           case WhitePromotion:
8892           case BlackPromotion:
8893           case WhiteNonPromotion:
8894           case BlackNonPromotion:
8895           case NormalMove:
8896           case WhiteCapturesEnPassant:
8897           case BlackCapturesEnPassant:
8898           case WhiteKingSideCastle:
8899           case WhiteQueenSideCastle:
8900           case BlackKingSideCastle:
8901           case BlackQueenSideCastle:
8902           case WhiteKingSideCastleWild:
8903           case WhiteQueenSideCastleWild:
8904           case BlackKingSideCastleWild:
8905           case BlackQueenSideCastleWild:
8906           /* PUSH Fabien */
8907           case WhiteHSideCastleFR:
8908           case WhiteASideCastleFR:
8909           case BlackHSideCastleFR:
8910           case BlackASideCastleFR:
8911           /* POP Fabien */
8912             fromX = currentMoveString[0] - AAA;
8913             fromY = currentMoveString[1] - ONE;
8914             toX = currentMoveString[2] - AAA;
8915             toY = currentMoveString[3] - ONE;
8916             promoChar = currentMoveString[4];
8917             break;
8918           case WhiteDrop:
8919           case BlackDrop:
8920             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8921             fromX = moveType == WhiteDrop ?
8922               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8923             (int) CharToPiece(ToLower(currentMoveString[0]));
8924             fromY = DROP_RANK;
8925             toX = currentMoveString[2] - AAA;
8926             toY = currentMoveString[3] - ONE;
8927             promoChar = NULLCHAR;
8928             break;
8929           case AmbiguousMove:
8930             /* bug? */
8931             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8932   if (appData.debugMode) {
8933     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8934     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8935     setbuf(debugFP, NULL);
8936   }
8937             DisplayError(buf, 0);
8938             return;
8939           case ImpossibleMove:
8940             /* bug? */
8941             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8942   if (appData.debugMode) {
8943     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8944     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8945     setbuf(debugFP, NULL);
8946   }
8947             DisplayError(buf, 0);
8948             return;
8949           case EndOfFile:
8950             if (boardIndex < backwardMostMove) {
8951                 /* Oops, gap.  How did that happen? */
8952                 DisplayError(_("Gap in move list"), 0);
8953                 return;
8954             }
8955             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8956             if (boardIndex > forwardMostMove) {
8957                 forwardMostMove = boardIndex;
8958             }
8959             return;
8960           case ElapsedTime:
8961             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8962                 strcat(parseList[boardIndex-1], " ");
8963                 strcat(parseList[boardIndex-1], yy_text);
8964             }
8965             continue;
8966           case Comment:
8967           case PGNTag:
8968           case NAG:
8969           default:
8970             /* ignore */
8971             continue;
8972           case WhiteWins:
8973           case BlackWins:
8974           case GameIsDrawn:
8975           case GameUnfinished:
8976             if (gameMode == IcsExamining) {
8977                 if (boardIndex < backwardMostMove) {
8978                     /* Oops, gap.  How did that happen? */
8979                     return;
8980                 }
8981                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8982                 return;
8983             }
8984             gameInfo.result = moveType;
8985             p = strchr(yy_text, '{');
8986             if (p == NULL) p = strchr(yy_text, '(');
8987             if (p == NULL) {
8988                 p = yy_text;
8989                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8990             } else {
8991                 q = strchr(p, *p == '{' ? '}' : ')');
8992                 if (q != NULL) *q = NULLCHAR;
8993                 p++;
8994             }
8995             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8996             gameInfo.resultDetails = StrSave(p);
8997             continue;
8998         }
8999         if (boardIndex >= forwardMostMove &&
9000             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9001             backwardMostMove = blackPlaysFirst ? 1 : 0;
9002             return;
9003         }
9004         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9005                                  fromY, fromX, toY, toX, promoChar,
9006                                  parseList[boardIndex]);
9007         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9008         /* currentMoveString is set as a side-effect of yylex */
9009         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9010         strcat(moveList[boardIndex], "\n");
9011         boardIndex++;
9012         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9013         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9014           case MT_NONE:
9015           case MT_STALEMATE:
9016           default:
9017             break;
9018           case MT_CHECK:
9019             if(gameInfo.variant != VariantShogi)
9020                 strcat(parseList[boardIndex - 1], "+");
9021             break;
9022           case MT_CHECKMATE:
9023           case MT_STAINMATE:
9024             strcat(parseList[boardIndex - 1], "#");
9025             break;
9026         }
9027     }
9028 }
9029
9030
9031 /* Apply a move to the given board  */
9032 void
9033 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9034 {
9035   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9036   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9037
9038     /* [HGM] compute & store e.p. status and castling rights for new position */
9039     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9040
9041       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9042       oldEP = (signed char)board[EP_STATUS];
9043       board[EP_STATUS] = EP_NONE;
9044
9045   if (fromY == DROP_RANK) {
9046         /* must be first */
9047         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9048             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9049             return;
9050         }
9051         piece = board[toY][toX] = (ChessSquare) fromX;
9052   } else {
9053       int i;
9054
9055       if( board[toY][toX] != EmptySquare )
9056            board[EP_STATUS] = EP_CAPTURE;
9057
9058       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9059            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9060                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9061       } else
9062       if( board[fromY][fromX] == WhitePawn ) {
9063            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9064                board[EP_STATUS] = EP_PAWN_MOVE;
9065            if( toY-fromY==2) {
9066                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9067                         gameInfo.variant != VariantBerolina || toX < fromX)
9068                       board[EP_STATUS] = toX | berolina;
9069                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9070                         gameInfo.variant != VariantBerolina || toX > fromX)
9071                       board[EP_STATUS] = toX;
9072            }
9073       } else
9074       if( board[fromY][fromX] == BlackPawn ) {
9075            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9076                board[EP_STATUS] = EP_PAWN_MOVE;
9077            if( toY-fromY== -2) {
9078                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9079                         gameInfo.variant != VariantBerolina || toX < fromX)
9080                       board[EP_STATUS] = toX | berolina;
9081                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9082                         gameInfo.variant != VariantBerolina || toX > fromX)
9083                       board[EP_STATUS] = toX;
9084            }
9085        }
9086
9087        for(i=0; i<nrCastlingRights; i++) {
9088            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9089               board[CASTLING][i] == toX   && castlingRank[i] == toY
9090              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9091        }
9092
9093      if (fromX == toX && fromY == toY) return;
9094
9095      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9096      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9097      if(gameInfo.variant == VariantKnightmate)
9098          king += (int) WhiteUnicorn - (int) WhiteKing;
9099
9100     /* Code added by Tord: */
9101     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9102     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9103         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9104       board[fromY][fromX] = EmptySquare;
9105       board[toY][toX] = EmptySquare;
9106       if((toX > fromX) != (piece == WhiteRook)) {
9107         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9108       } else {
9109         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9110       }
9111     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9112                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9113       board[fromY][fromX] = EmptySquare;
9114       board[toY][toX] = EmptySquare;
9115       if((toX > fromX) != (piece == BlackRook)) {
9116         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9117       } else {
9118         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9119       }
9120     /* End of code added by Tord */
9121
9122     } else if (board[fromY][fromX] == king
9123         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9124         && toY == fromY && toX > fromX+1) {
9125         board[fromY][fromX] = EmptySquare;
9126         board[toY][toX] = king;
9127         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9128         board[fromY][BOARD_RGHT-1] = EmptySquare;
9129     } else if (board[fromY][fromX] == king
9130         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9131                && toY == fromY && toX < fromX-1) {
9132         board[fromY][fromX] = EmptySquare;
9133         board[toY][toX] = king;
9134         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9135         board[fromY][BOARD_LEFT] = EmptySquare;
9136     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9137                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9138                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9139                ) {
9140         /* white pawn promotion */
9141         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9142         if(gameInfo.variant==VariantBughouse ||
9143            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9144             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9145         board[fromY][fromX] = EmptySquare;
9146     } else if ((fromY >= BOARD_HEIGHT>>1)
9147                && (toX != fromX)
9148                && gameInfo.variant != VariantXiangqi
9149                && gameInfo.variant != VariantBerolina
9150                && (board[fromY][fromX] == WhitePawn)
9151                && (board[toY][toX] == EmptySquare)) {
9152         board[fromY][fromX] = EmptySquare;
9153         board[toY][toX] = WhitePawn;
9154         captured = board[toY - 1][toX];
9155         board[toY - 1][toX] = EmptySquare;
9156     } else if ((fromY == BOARD_HEIGHT-4)
9157                && (toX == fromX)
9158                && gameInfo.variant == VariantBerolina
9159                && (board[fromY][fromX] == WhitePawn)
9160                && (board[toY][toX] == EmptySquare)) {
9161         board[fromY][fromX] = EmptySquare;
9162         board[toY][toX] = WhitePawn;
9163         if(oldEP & EP_BEROLIN_A) {
9164                 captured = board[fromY][fromX-1];
9165                 board[fromY][fromX-1] = EmptySquare;
9166         }else{  captured = board[fromY][fromX+1];
9167                 board[fromY][fromX+1] = EmptySquare;
9168         }
9169     } else if (board[fromY][fromX] == king
9170         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9171                && toY == fromY && toX > fromX+1) {
9172         board[fromY][fromX] = EmptySquare;
9173         board[toY][toX] = king;
9174         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9175         board[fromY][BOARD_RGHT-1] = EmptySquare;
9176     } else if (board[fromY][fromX] == king
9177         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9178                && toY == fromY && toX < fromX-1) {
9179         board[fromY][fromX] = EmptySquare;
9180         board[toY][toX] = king;
9181         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9182         board[fromY][BOARD_LEFT] = EmptySquare;
9183     } else if (fromY == 7 && fromX == 3
9184                && board[fromY][fromX] == BlackKing
9185                && toY == 7 && toX == 5) {
9186         board[fromY][fromX] = EmptySquare;
9187         board[toY][toX] = BlackKing;
9188         board[fromY][7] = EmptySquare;
9189         board[toY][4] = BlackRook;
9190     } else if (fromY == 7 && fromX == 3
9191                && board[fromY][fromX] == BlackKing
9192                && toY == 7 && toX == 1) {
9193         board[fromY][fromX] = EmptySquare;
9194         board[toY][toX] = BlackKing;
9195         board[fromY][0] = EmptySquare;
9196         board[toY][2] = BlackRook;
9197     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9198                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9199                && toY < promoRank && promoChar
9200                ) {
9201         /* black pawn promotion */
9202         board[toY][toX] = CharToPiece(ToLower(promoChar));
9203         if(gameInfo.variant==VariantBughouse ||
9204            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9205             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9206         board[fromY][fromX] = EmptySquare;
9207     } else if ((fromY < BOARD_HEIGHT>>1)
9208                && (toX != fromX)
9209                && gameInfo.variant != VariantXiangqi
9210                && gameInfo.variant != VariantBerolina
9211                && (board[fromY][fromX] == BlackPawn)
9212                && (board[toY][toX] == EmptySquare)) {
9213         board[fromY][fromX] = EmptySquare;
9214         board[toY][toX] = BlackPawn;
9215         captured = board[toY + 1][toX];
9216         board[toY + 1][toX] = EmptySquare;
9217     } else if ((fromY == 3)
9218                && (toX == fromX)
9219                && gameInfo.variant == VariantBerolina
9220                && (board[fromY][fromX] == BlackPawn)
9221                && (board[toY][toX] == EmptySquare)) {
9222         board[fromY][fromX] = EmptySquare;
9223         board[toY][toX] = BlackPawn;
9224         if(oldEP & EP_BEROLIN_A) {
9225                 captured = board[fromY][fromX-1];
9226                 board[fromY][fromX-1] = EmptySquare;
9227         }else{  captured = board[fromY][fromX+1];
9228                 board[fromY][fromX+1] = EmptySquare;
9229         }
9230     } else {
9231         board[toY][toX] = board[fromY][fromX];
9232         board[fromY][fromX] = EmptySquare;
9233     }
9234   }
9235
9236     if (gameInfo.holdingsWidth != 0) {
9237
9238       /* !!A lot more code needs to be written to support holdings  */
9239       /* [HGM] OK, so I have written it. Holdings are stored in the */
9240       /* penultimate board files, so they are automaticlly stored   */
9241       /* in the game history.                                       */
9242       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9243                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9244         /* Delete from holdings, by decreasing count */
9245         /* and erasing image if necessary            */
9246         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9247         if(p < (int) BlackPawn) { /* white drop */
9248              p -= (int)WhitePawn;
9249                  p = PieceToNumber((ChessSquare)p);
9250              if(p >= gameInfo.holdingsSize) p = 0;
9251              if(--board[p][BOARD_WIDTH-2] <= 0)
9252                   board[p][BOARD_WIDTH-1] = EmptySquare;
9253              if((int)board[p][BOARD_WIDTH-2] < 0)
9254                         board[p][BOARD_WIDTH-2] = 0;
9255         } else {                  /* black drop */
9256              p -= (int)BlackPawn;
9257                  p = PieceToNumber((ChessSquare)p);
9258              if(p >= gameInfo.holdingsSize) p = 0;
9259              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9260                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9261              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9262                         board[BOARD_HEIGHT-1-p][1] = 0;
9263         }
9264       }
9265       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9266           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9267         /* [HGM] holdings: Add to holdings, if holdings exist */
9268         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9269                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9270                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9271         }
9272         p = (int) captured;
9273         if (p >= (int) BlackPawn) {
9274           p -= (int)BlackPawn;
9275           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9276                   /* in Shogi restore piece to its original  first */
9277                   captured = (ChessSquare) (DEMOTED captured);
9278                   p = DEMOTED p;
9279           }
9280           p = PieceToNumber((ChessSquare)p);
9281           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9282           board[p][BOARD_WIDTH-2]++;
9283           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9284         } else {
9285           p -= (int)WhitePawn;
9286           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9287                   captured = (ChessSquare) (DEMOTED captured);
9288                   p = DEMOTED p;
9289           }
9290           p = PieceToNumber((ChessSquare)p);
9291           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9292           board[BOARD_HEIGHT-1-p][1]++;
9293           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9294         }
9295       }
9296     } else if (gameInfo.variant == VariantAtomic) {
9297       if (captured != EmptySquare) {
9298         int y, x;
9299         for (y = toY-1; y <= toY+1; y++) {
9300           for (x = toX-1; x <= toX+1; x++) {
9301             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9302                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9303               board[y][x] = EmptySquare;
9304             }
9305           }
9306         }
9307         board[toY][toX] = EmptySquare;
9308       }
9309     }
9310     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9311         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9312     } else
9313     if(promoChar == '+') {
9314         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9315         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9316     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9317         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9318     }
9319     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9320                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9321         // [HGM] superchess: take promotion piece out of holdings
9322         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9323         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9324             if(!--board[k][BOARD_WIDTH-2])
9325                 board[k][BOARD_WIDTH-1] = EmptySquare;
9326         } else {
9327             if(!--board[BOARD_HEIGHT-1-k][1])
9328                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9329         }
9330     }
9331
9332 }
9333
9334 /* Updates forwardMostMove */
9335 void
9336 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9337 {
9338 //    forwardMostMove++; // [HGM] bare: moved downstream
9339
9340     (void) CoordsToAlgebraic(boards[forwardMostMove],
9341                              PosFlags(forwardMostMove),
9342                              fromY, fromX, toY, toX, promoChar,
9343                              parseList[forwardMostMove]);
9344
9345     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9346         int timeLeft; static int lastLoadFlag=0; int king, piece;
9347         piece = boards[forwardMostMove][fromY][fromX];
9348         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9349         if(gameInfo.variant == VariantKnightmate)
9350             king += (int) WhiteUnicorn - (int) WhiteKing;
9351         if(forwardMostMove == 0) {
9352             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9353                 fprintf(serverMoves, "%s;", UserName());
9354             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9355                 fprintf(serverMoves, "%s;", second.tidy);
9356             fprintf(serverMoves, "%s;", first.tidy);
9357             if(gameMode == MachinePlaysWhite)
9358                 fprintf(serverMoves, "%s;", UserName());
9359             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9360                 fprintf(serverMoves, "%s;", second.tidy);
9361         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9362         lastLoadFlag = loadFlag;
9363         // print base move
9364         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9365         // print castling suffix
9366         if( toY == fromY && piece == king ) {
9367             if(toX-fromX > 1)
9368                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9369             if(fromX-toX >1)
9370                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9371         }
9372         // e.p. suffix
9373         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9374              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9375              boards[forwardMostMove][toY][toX] == EmptySquare
9376              && fromX != toX && fromY != toY)
9377                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9378         // promotion suffix
9379         if(promoChar != NULLCHAR)
9380                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9381         if(!loadFlag) {
9382                 char buf[MOVE_LEN*2], *p; int len;
9383             fprintf(serverMoves, "/%d/%d",
9384                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9385             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9386             else                      timeLeft = blackTimeRemaining/1000;
9387             fprintf(serverMoves, "/%d", timeLeft);
9388                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9389                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9390                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9391             fprintf(serverMoves, "/%s", buf);
9392         }
9393         fflush(serverMoves);
9394     }
9395
9396     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9397         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9398       return;
9399     }
9400     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9401     if (commentList[forwardMostMove+1] != NULL) {
9402         free(commentList[forwardMostMove+1]);
9403         commentList[forwardMostMove+1] = NULL;
9404     }
9405     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9406     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9407     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9408     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9409     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9410     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9411     adjustedClock = FALSE;
9412     gameInfo.result = GameUnfinished;
9413     if (gameInfo.resultDetails != NULL) {
9414         free(gameInfo.resultDetails);
9415         gameInfo.resultDetails = NULL;
9416     }
9417     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9418                               moveList[forwardMostMove - 1]);
9419     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9420       case MT_NONE:
9421       case MT_STALEMATE:
9422       default:
9423         break;
9424       case MT_CHECK:
9425         if(gameInfo.variant != VariantShogi)
9426             strcat(parseList[forwardMostMove - 1], "+");
9427         break;
9428       case MT_CHECKMATE:
9429       case MT_STAINMATE:
9430         strcat(parseList[forwardMostMove - 1], "#");
9431         break;
9432     }
9433     if (appData.debugMode) {
9434         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9435     }
9436
9437 }
9438
9439 /* Updates currentMove if not pausing */
9440 void
9441 ShowMove (int fromX, int fromY, int toX, int toY)
9442 {
9443     int instant = (gameMode == PlayFromGameFile) ?
9444         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9445     if(appData.noGUI) return;
9446     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9447         if (!instant) {
9448             if (forwardMostMove == currentMove + 1) {
9449                 AnimateMove(boards[forwardMostMove - 1],
9450                             fromX, fromY, toX, toY);
9451             }
9452             if (appData.highlightLastMove) {
9453                 SetHighlights(fromX, fromY, toX, toY);
9454             }
9455         }
9456         currentMove = forwardMostMove;
9457     }
9458
9459     if (instant) return;
9460
9461     DisplayMove(currentMove - 1);
9462     DrawPosition(FALSE, boards[currentMove]);
9463     DisplayBothClocks();
9464     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9465 }
9466
9467 void
9468 SendEgtPath (ChessProgramState *cps)
9469 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9470         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9471
9472         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9473
9474         while(*p) {
9475             char c, *q = name+1, *r, *s;
9476
9477             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9478             while(*p && *p != ',') *q++ = *p++;
9479             *q++ = ':'; *q = 0;
9480             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9481                 strcmp(name, ",nalimov:") == 0 ) {
9482                 // take nalimov path from the menu-changeable option first, if it is defined
9483               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9484                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9485             } else
9486             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9487                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9488                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9489                 s = r = StrStr(s, ":") + 1; // beginning of path info
9490                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9491                 c = *r; *r = 0;             // temporarily null-terminate path info
9492                     *--q = 0;               // strip of trailig ':' from name
9493                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9494                 *r = c;
9495                 SendToProgram(buf,cps);     // send egtbpath command for this format
9496             }
9497             if(*p == ',') p++; // read away comma to position for next format name
9498         }
9499 }
9500
9501 void
9502 InitChessProgram (ChessProgramState *cps, int setup)
9503 /* setup needed to setup FRC opening position */
9504 {
9505     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9506     if (appData.noChessProgram) return;
9507     hintRequested = FALSE;
9508     bookRequested = FALSE;
9509
9510     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9511     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9512     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9513     if(cps->memSize) { /* [HGM] memory */
9514       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9515         SendToProgram(buf, cps);
9516     }
9517     SendEgtPath(cps); /* [HGM] EGT */
9518     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9519       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9520         SendToProgram(buf, cps);
9521     }
9522
9523     SendToProgram(cps->initString, cps);
9524     if (gameInfo.variant != VariantNormal &&
9525         gameInfo.variant != VariantLoadable
9526         /* [HGM] also send variant if board size non-standard */
9527         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9528                                             ) {
9529       char *v = VariantName(gameInfo.variant);
9530       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9531         /* [HGM] in protocol 1 we have to assume all variants valid */
9532         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9533         DisplayFatalError(buf, 0, 1);
9534         return;
9535       }
9536
9537       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9538       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9539       if( gameInfo.variant == VariantXiangqi )
9540            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9541       if( gameInfo.variant == VariantShogi )
9542            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9543       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9544            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9545       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9546           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9547            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9548       if( gameInfo.variant == VariantCourier )
9549            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9550       if( gameInfo.variant == VariantSuper )
9551            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9552       if( gameInfo.variant == VariantGreat )
9553            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9554       if( gameInfo.variant == VariantSChess )
9555            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9556       if( gameInfo.variant == VariantGrand )
9557            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9558
9559       if(overruled) {
9560         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9561                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9562            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9563            if(StrStr(cps->variants, b) == NULL) {
9564                // specific sized variant not known, check if general sizing allowed
9565                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9566                    if(StrStr(cps->variants, "boardsize") == NULL) {
9567                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9568                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9569                        DisplayFatalError(buf, 0, 1);
9570                        return;
9571                    }
9572                    /* [HGM] here we really should compare with the maximum supported board size */
9573                }
9574            }
9575       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9576       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9577       SendToProgram(buf, cps);
9578     }
9579     currentlyInitializedVariant = gameInfo.variant;
9580
9581     /* [HGM] send opening position in FRC to first engine */
9582     if(setup) {
9583           SendToProgram("force\n", cps);
9584           SendBoard(cps, 0);
9585           /* engine is now in force mode! Set flag to wake it up after first move. */
9586           setboardSpoiledMachineBlack = 1;
9587     }
9588
9589     if (cps->sendICS) {
9590       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9591       SendToProgram(buf, cps);
9592     }
9593     cps->maybeThinking = FALSE;
9594     cps->offeredDraw = 0;
9595     if (!appData.icsActive) {
9596         SendTimeControl(cps, movesPerSession, timeControl,
9597                         timeIncrement, appData.searchDepth,
9598                         searchTime);
9599     }
9600     if (appData.showThinking
9601         // [HGM] thinking: four options require thinking output to be sent
9602         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9603                                 ) {
9604         SendToProgram("post\n", cps);
9605     }
9606     SendToProgram("hard\n", cps);
9607     if (!appData.ponderNextMove) {
9608         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9609            it without being sure what state we are in first.  "hard"
9610            is not a toggle, so that one is OK.
9611          */
9612         SendToProgram("easy\n", cps);
9613     }
9614     if (cps->usePing) {
9615       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9616       SendToProgram(buf, cps);
9617     }
9618     cps->initDone = TRUE;
9619     ClearEngineOutputPane(cps == &second);
9620 }
9621
9622
9623 void
9624 StartChessProgram (ChessProgramState *cps)
9625 {
9626     char buf[MSG_SIZ];
9627     int err;
9628
9629     if (appData.noChessProgram) return;
9630     cps->initDone = FALSE;
9631
9632     if (strcmp(cps->host, "localhost") == 0) {
9633         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9634     } else if (*appData.remoteShell == NULLCHAR) {
9635         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9636     } else {
9637         if (*appData.remoteUser == NULLCHAR) {
9638           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9639                     cps->program);
9640         } else {
9641           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9642                     cps->host, appData.remoteUser, cps->program);
9643         }
9644         err = StartChildProcess(buf, "", &cps->pr);
9645     }
9646
9647     if (err != 0) {
9648       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9649         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9650         if(cps != &first) return;
9651         appData.noChessProgram = TRUE;
9652         ThawUI();
9653         SetNCPMode();
9654 //      DisplayFatalError(buf, err, 1);
9655 //      cps->pr = NoProc;
9656 //      cps->isr = NULL;
9657         return;
9658     }
9659
9660     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9661     if (cps->protocolVersion > 1) {
9662       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9663       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9664       cps->comboCnt = 0;  //                and values of combo boxes
9665       SendToProgram(buf, cps);
9666     } else {
9667       SendToProgram("xboard\n", cps);
9668     }
9669 }
9670
9671 void
9672 TwoMachinesEventIfReady P((void))
9673 {
9674   static int curMess = 0;
9675   if (first.lastPing != first.lastPong) {
9676     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9677     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9678     return;
9679   }
9680   if (second.lastPing != second.lastPong) {
9681     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9682     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9683     return;
9684   }
9685   DisplayMessage("", ""); curMess = 0;
9686   ThawUI();
9687   TwoMachinesEvent();
9688 }
9689
9690 char *
9691 MakeName (char *template)
9692 {
9693     time_t clock;
9694     struct tm *tm;
9695     static char buf[MSG_SIZ];
9696     char *p = buf;
9697     int i;
9698
9699     clock = time((time_t *)NULL);
9700     tm = localtime(&clock);
9701
9702     while(*p++ = *template++) if(p[-1] == '%') {
9703         switch(*template++) {
9704           case 0:   *p = 0; return buf;
9705           case 'Y': i = tm->tm_year+1900; break;
9706           case 'y': i = tm->tm_year-100; break;
9707           case 'M': i = tm->tm_mon+1; break;
9708           case 'd': i = tm->tm_mday; break;
9709           case 'h': i = tm->tm_hour; break;
9710           case 'm': i = tm->tm_min; break;
9711           case 's': i = tm->tm_sec; break;
9712           default:  i = 0;
9713         }
9714         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9715     }
9716     return buf;
9717 }
9718
9719 int
9720 CountPlayers (char *p)
9721 {
9722     int n = 0;
9723     while(p = strchr(p, '\n')) p++, n++; // count participants
9724     return n;
9725 }
9726
9727 FILE *
9728 WriteTourneyFile (char *results, FILE *f)
9729 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9730     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9731     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9732         // create a file with tournament description
9733         fprintf(f, "-participants {%s}\n", appData.participants);
9734         fprintf(f, "-seedBase %d\n", appData.seedBase);
9735         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9736         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9737         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9738         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9739         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9740         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9741         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9742         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9743         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9744         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9745         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9746         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9747         if(searchTime > 0)
9748                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9749         else {
9750                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9751                 fprintf(f, "-tc %s\n", appData.timeControl);
9752                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9753         }
9754         fprintf(f, "-results \"%s\"\n", results);
9755     }
9756     return f;
9757 }
9758
9759 #define MAXENGINES 1000
9760 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9761
9762 void
9763 Substitute (char *participants, int expunge)
9764 {
9765     int i, changed, changes=0, nPlayers=0;
9766     char *p, *q, *r, buf[MSG_SIZ];
9767     if(participants == NULL) return;
9768     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9769     r = p = participants; q = appData.participants;
9770     while(*p && *p == *q) {
9771         if(*p == '\n') r = p+1, nPlayers++;
9772         p++; q++;
9773     }
9774     if(*p) { // difference
9775         while(*p && *p++ != '\n');
9776         while(*q && *q++ != '\n');
9777       changed = nPlayers;
9778         changes = 1 + (strcmp(p, q) != 0);
9779     }
9780     if(changes == 1) { // a single engine mnemonic was changed
9781         q = r; while(*q) nPlayers += (*q++ == '\n');
9782         p = buf; while(*r && (*p = *r++) != '\n') p++;
9783         *p = NULLCHAR;
9784         NamesToList(firstChessProgramNames, command, mnemonic);
9785         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9786         if(mnemonic[i]) { // The substitute is valid
9787             FILE *f;
9788             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9789                 flock(fileno(f), LOCK_EX);
9790                 ParseArgsFromFile(f);
9791                 fseek(f, 0, SEEK_SET);
9792                 FREE(appData.participants); appData.participants = participants;
9793                 if(expunge) { // erase results of replaced engine
9794                     int len = strlen(appData.results), w, b, dummy;
9795                     for(i=0; i<len; i++) {
9796                         Pairing(i, nPlayers, &w, &b, &dummy);
9797                         if((w == changed || b == changed) && appData.results[i] == '*') {
9798                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9799                             fclose(f);
9800                             return;
9801                         }
9802                     }
9803                     for(i=0; i<len; i++) {
9804                         Pairing(i, nPlayers, &w, &b, &dummy);
9805                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9806                     }
9807                 }
9808                 WriteTourneyFile(appData.results, f);
9809                 fclose(f); // release lock
9810                 return;
9811             }
9812         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9813     }
9814     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9815     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9816     free(participants);
9817     return;
9818 }
9819
9820 int
9821 CreateTourney (char *name)
9822 {
9823         FILE *f;
9824         if(matchMode && strcmp(name, appData.tourneyFile)) {
9825              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9826         }
9827         if(name[0] == NULLCHAR) {
9828             if(appData.participants[0])
9829                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9830             return 0;
9831         }
9832         f = fopen(name, "r");
9833         if(f) { // file exists
9834             ASSIGN(appData.tourneyFile, name);
9835             ParseArgsFromFile(f); // parse it
9836         } else {
9837             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9838             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9839                 DisplayError(_("Not enough participants"), 0);
9840                 return 0;
9841             }
9842             ASSIGN(appData.tourneyFile, name);
9843             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9844             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9845         }
9846         fclose(f);
9847         appData.noChessProgram = FALSE;
9848         appData.clockMode = TRUE;
9849         SetGNUMode();
9850         return 1;
9851 }
9852
9853 void
9854 NamesToList (char *names, char **engineList, char **engineMnemonic)
9855 {
9856     char buf[MSG_SIZ], *p, *q;
9857     int i=1;
9858     while(*names) {
9859         p = names; q = buf;
9860         while(*p && *p != '\n') *q++ = *p++;
9861         *q = 0;
9862         if(engineList[i]) free(engineList[i]);
9863         engineList[i] = strdup(buf);
9864         if(*p == '\n') p++;
9865         TidyProgramName(engineList[i], "localhost", buf);
9866         if(engineMnemonic[i]) free(engineMnemonic[i]);
9867         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9868             strcat(buf, " (");
9869             sscanf(q + 8, "%s", buf + strlen(buf));
9870             strcat(buf, ")");
9871         }
9872         engineMnemonic[i] = strdup(buf);
9873         names = p; i++;
9874       if(i > MAXENGINES - 2) break;
9875     }
9876     engineList[i] = engineMnemonic[i] = NULL;
9877 }
9878
9879 // following implemented as macro to avoid type limitations
9880 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9881
9882 void
9883 SwapEngines (int n)
9884 {   // swap settings for first engine and other engine (so far only some selected options)
9885     int h;
9886     char *p;
9887     if(n == 0) return;
9888     SWAP(directory, p)
9889     SWAP(chessProgram, p)
9890     SWAP(isUCI, h)
9891     SWAP(hasOwnBookUCI, h)
9892     SWAP(protocolVersion, h)
9893     SWAP(reuse, h)
9894     SWAP(scoreIsAbsolute, h)
9895     SWAP(timeOdds, h)
9896     SWAP(logo, p)
9897     SWAP(pgnName, p)
9898     SWAP(pvSAN, h)
9899     SWAP(engOptions, p)
9900 }
9901
9902 void
9903 SetPlayer (int player)
9904 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9905     int i;
9906     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9907     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9908     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9909     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9910     if(mnemonic[i]) {
9911         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9912         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9913         appData.firstHasOwnBookUCI = !appData.defNoBook;
9914         ParseArgsFromString(buf);
9915     }
9916     free(engineName);
9917 }
9918
9919 int
9920 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9921 {   // determine players from game number
9922     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9923
9924     if(appData.tourneyType == 0) {
9925         roundsPerCycle = (nPlayers - 1) | 1;
9926         pairingsPerRound = nPlayers / 2;
9927     } else if(appData.tourneyType > 0) {
9928         roundsPerCycle = nPlayers - appData.tourneyType;
9929         pairingsPerRound = appData.tourneyType;
9930     }
9931     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9932     gamesPerCycle = gamesPerRound * roundsPerCycle;
9933     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9934     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9935     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9936     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9937     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9938     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9939
9940     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9941     if(appData.roundSync) *syncInterval = gamesPerRound;
9942
9943     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9944
9945     if(appData.tourneyType == 0) {
9946         if(curPairing == (nPlayers-1)/2 ) {
9947             *whitePlayer = curRound;
9948             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9949         } else {
9950             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9951             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9952             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9953             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9954         }
9955     } else if(appData.tourneyType > 0) {
9956         *whitePlayer = curPairing;
9957         *blackPlayer = curRound + appData.tourneyType;
9958     }
9959
9960     // take care of white/black alternation per round. 
9961     // For cycles and games this is already taken care of by default, derived from matchGame!
9962     return curRound & 1;
9963 }
9964
9965 int
9966 NextTourneyGame (int nr, int *swapColors)
9967 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9968     char *p, *q;
9969     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9970     FILE *tf;
9971     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9972     tf = fopen(appData.tourneyFile, "r");
9973     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9974     ParseArgsFromFile(tf); fclose(tf);
9975     InitTimeControls(); // TC might be altered from tourney file
9976
9977     nPlayers = CountPlayers(appData.participants); // count participants
9978     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
9979     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9980
9981     if(syncInterval) {
9982         p = q = appData.results;
9983         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9984         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9985             DisplayMessage(_("Waiting for other game(s)"),"");
9986             waitingForGame = TRUE;
9987             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9988             return 0;
9989         }
9990         waitingForGame = FALSE;
9991     }
9992
9993     if(appData.tourneyType < 0) {
9994         if(nr>=0 && !pairingReceived) {
9995             char buf[1<<16];
9996             if(pairing.pr == NoProc) {
9997                 if(!appData.pairingEngine[0]) {
9998                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
9999                     return 0;
10000                 }
10001                 StartChessProgram(&pairing); // starts the pairing engine
10002             }
10003             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10004             SendToProgram(buf, &pairing);
10005             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10006             SendToProgram(buf, &pairing);
10007             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10008         }
10009         pairingReceived = 0;                              // ... so we continue here 
10010         *swapColors = 0;
10011         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10012         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10013         matchGame = 1; roundNr = nr / syncInterval + 1;
10014     }
10015
10016     if(first.pr != NoProc || second.pr != NoProc) return 1; // engines already loaded
10017
10018     // redefine engines, engine dir, etc.
10019     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10020     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
10021     SwapEngines(1);
10022     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
10023     SwapEngines(1);         // and make that valid for second engine by swapping
10024     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10025     InitEngine(&second, 1);
10026     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10027     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10028     return 1;
10029 }
10030
10031 void
10032 NextMatchGame ()
10033 {   // performs game initialization that does not invoke engines, and then tries to start the game
10034     int res, firstWhite, swapColors = 0;
10035     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10036     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10037     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10038     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10039     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10040     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10041     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10042     Reset(FALSE, first.pr != NoProc);
10043     res = LoadGameOrPosition(matchGame); // setup game
10044     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10045     if(!res) return; // abort when bad game/pos file
10046     TwoMachinesEvent();
10047 }
10048
10049 void
10050 UserAdjudicationEvent (int result)
10051 {
10052     ChessMove gameResult = GameIsDrawn;
10053
10054     if( result > 0 ) {
10055         gameResult = WhiteWins;
10056     }
10057     else if( result < 0 ) {
10058         gameResult = BlackWins;
10059     }
10060
10061     if( gameMode == TwoMachinesPlay ) {
10062         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10063     }
10064 }
10065
10066
10067 // [HGM] save: calculate checksum of game to make games easily identifiable
10068 int
10069 StringCheckSum (char *s)
10070 {
10071         int i = 0;
10072         if(s==NULL) return 0;
10073         while(*s) i = i*259 + *s++;
10074         return i;
10075 }
10076
10077 int
10078 GameCheckSum ()
10079 {
10080         int i, sum=0;
10081         for(i=backwardMostMove; i<forwardMostMove; i++) {
10082                 sum += pvInfoList[i].depth;
10083                 sum += StringCheckSum(parseList[i]);
10084                 sum += StringCheckSum(commentList[i]);
10085                 sum *= 261;
10086         }
10087         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10088         return sum + StringCheckSum(commentList[i]);
10089 } // end of save patch
10090
10091 void
10092 GameEnds (ChessMove result, char *resultDetails, int whosays)
10093 {
10094     GameMode nextGameMode;
10095     int isIcsGame;
10096     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10097
10098     if(endingGame) return; /* [HGM] crash: forbid recursion */
10099     endingGame = 1;
10100     if(twoBoards) { // [HGM] dual: switch back to one board
10101         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10102         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10103     }
10104     if (appData.debugMode) {
10105       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10106               result, resultDetails ? resultDetails : "(null)", whosays);
10107     }
10108
10109     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10110
10111     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10112         /* If we are playing on ICS, the server decides when the
10113            game is over, but the engine can offer to draw, claim
10114            a draw, or resign.
10115          */
10116 #if ZIPPY
10117         if (appData.zippyPlay && first.initDone) {
10118             if (result == GameIsDrawn) {
10119                 /* In case draw still needs to be claimed */
10120                 SendToICS(ics_prefix);
10121                 SendToICS("draw\n");
10122             } else if (StrCaseStr(resultDetails, "resign")) {
10123                 SendToICS(ics_prefix);
10124                 SendToICS("resign\n");
10125             }
10126         }
10127 #endif
10128         endingGame = 0; /* [HGM] crash */
10129         return;
10130     }
10131
10132     /* If we're loading the game from a file, stop */
10133     if (whosays == GE_FILE) {
10134       (void) StopLoadGameTimer();
10135       gameFileFP = NULL;
10136     }
10137
10138     /* Cancel draw offers */
10139     first.offeredDraw = second.offeredDraw = 0;
10140
10141     /* If this is an ICS game, only ICS can really say it's done;
10142        if not, anyone can. */
10143     isIcsGame = (gameMode == IcsPlayingWhite ||
10144                  gameMode == IcsPlayingBlack ||
10145                  gameMode == IcsObserving    ||
10146                  gameMode == IcsExamining);
10147
10148     if (!isIcsGame || whosays == GE_ICS) {
10149         /* OK -- not an ICS game, or ICS said it was done */
10150         StopClocks();
10151         if (!isIcsGame && !appData.noChessProgram)
10152           SetUserThinkingEnables();
10153
10154         /* [HGM] if a machine claims the game end we verify this claim */
10155         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10156             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10157                 char claimer;
10158                 ChessMove trueResult = (ChessMove) -1;
10159
10160                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10161                                             first.twoMachinesColor[0] :
10162                                             second.twoMachinesColor[0] ;
10163
10164                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10165                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10166                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10167                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10168                 } else
10169                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10170                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10171                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10172                 } else
10173                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10174                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10175                 }
10176
10177                 // now verify win claims, but not in drop games, as we don't understand those yet
10178                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10179                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10180                     (result == WhiteWins && claimer == 'w' ||
10181                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10182                       if (appData.debugMode) {
10183                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10184                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10185                       }
10186                       if(result != trueResult) {
10187                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10188                               result = claimer == 'w' ? BlackWins : WhiteWins;
10189                               resultDetails = buf;
10190                       }
10191                 } else
10192                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10193                     && (forwardMostMove <= backwardMostMove ||
10194                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10195                         (claimer=='b')==(forwardMostMove&1))
10196                                                                                   ) {
10197                       /* [HGM] verify: draws that were not flagged are false claims */
10198                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10199                       result = claimer == 'w' ? BlackWins : WhiteWins;
10200                       resultDetails = buf;
10201                 }
10202                 /* (Claiming a loss is accepted no questions asked!) */
10203             }
10204             /* [HGM] bare: don't allow bare King to win */
10205             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10206                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10207                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10208                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10209                && result != GameIsDrawn)
10210             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10211                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10212                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10213                         if(p >= 0 && p <= (int)WhiteKing) k++;
10214                 }
10215                 if (appData.debugMode) {
10216                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10217                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10218                 }
10219                 if(k <= 1) {
10220                         result = GameIsDrawn;
10221                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10222                         resultDetails = buf;
10223                 }
10224             }
10225         }
10226
10227
10228         if(serverMoves != NULL && !loadFlag) { char c = '=';
10229             if(result==WhiteWins) c = '+';
10230             if(result==BlackWins) c = '-';
10231             if(resultDetails != NULL)
10232                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10233         }
10234         if (resultDetails != NULL) {
10235             gameInfo.result = result;
10236             gameInfo.resultDetails = StrSave(resultDetails);
10237
10238             /* display last move only if game was not loaded from file */
10239             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10240                 DisplayMove(currentMove - 1);
10241
10242             if (forwardMostMove != 0) {
10243                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10244                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10245                                                                 ) {
10246                     if (*appData.saveGameFile != NULLCHAR) {
10247                         SaveGameToFile(appData.saveGameFile, TRUE);
10248                     } else if (appData.autoSaveGames) {
10249                         AutoSaveGame();
10250                     }
10251                     if (*appData.savePositionFile != NULLCHAR) {
10252                         SavePositionToFile(appData.savePositionFile);
10253                     }
10254                 }
10255             }
10256
10257             /* Tell program how game ended in case it is learning */
10258             /* [HGM] Moved this to after saving the PGN, just in case */
10259             /* engine died and we got here through time loss. In that */
10260             /* case we will get a fatal error writing the pipe, which */
10261             /* would otherwise lose us the PGN.                       */
10262             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10263             /* output during GameEnds should never be fatal anymore   */
10264             if (gameMode == MachinePlaysWhite ||
10265                 gameMode == MachinePlaysBlack ||
10266                 gameMode == TwoMachinesPlay ||
10267                 gameMode == IcsPlayingWhite ||
10268                 gameMode == IcsPlayingBlack ||
10269                 gameMode == BeginningOfGame) {
10270                 char buf[MSG_SIZ];
10271                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10272                         resultDetails);
10273                 if (first.pr != NoProc) {
10274                     SendToProgram(buf, &first);
10275                 }
10276                 if (second.pr != NoProc &&
10277                     gameMode == TwoMachinesPlay) {
10278                     SendToProgram(buf, &second);
10279                 }
10280             }
10281         }
10282
10283         if (appData.icsActive) {
10284             if (appData.quietPlay &&
10285                 (gameMode == IcsPlayingWhite ||
10286                  gameMode == IcsPlayingBlack)) {
10287                 SendToICS(ics_prefix);
10288                 SendToICS("set shout 1\n");
10289             }
10290             nextGameMode = IcsIdle;
10291             ics_user_moved = FALSE;
10292             /* clean up premove.  It's ugly when the game has ended and the
10293              * premove highlights are still on the board.
10294              */
10295             if (gotPremove) {
10296               gotPremove = FALSE;
10297               ClearPremoveHighlights();
10298               DrawPosition(FALSE, boards[currentMove]);
10299             }
10300             if (whosays == GE_ICS) {
10301                 switch (result) {
10302                 case WhiteWins:
10303                     if (gameMode == IcsPlayingWhite)
10304                         PlayIcsWinSound();
10305                     else if(gameMode == IcsPlayingBlack)
10306                         PlayIcsLossSound();
10307                     break;
10308                 case BlackWins:
10309                     if (gameMode == IcsPlayingBlack)
10310                         PlayIcsWinSound();
10311                     else if(gameMode == IcsPlayingWhite)
10312                         PlayIcsLossSound();
10313                     break;
10314                 case GameIsDrawn:
10315                     PlayIcsDrawSound();
10316                     break;
10317                 default:
10318                     PlayIcsUnfinishedSound();
10319                 }
10320             }
10321         } else if (gameMode == EditGame ||
10322                    gameMode == PlayFromGameFile ||
10323                    gameMode == AnalyzeMode ||
10324                    gameMode == AnalyzeFile) {
10325             nextGameMode = gameMode;
10326         } else {
10327             nextGameMode = EndOfGame;
10328         }
10329         pausing = FALSE;
10330         ModeHighlight();
10331     } else {
10332         nextGameMode = gameMode;
10333     }
10334
10335     if (appData.noChessProgram) {
10336         gameMode = nextGameMode;
10337         ModeHighlight();
10338         endingGame = 0; /* [HGM] crash */
10339         return;
10340     }
10341
10342     if (first.reuse) {
10343         /* Put first chess program into idle state */
10344         if (first.pr != NoProc &&
10345             (gameMode == MachinePlaysWhite ||
10346              gameMode == MachinePlaysBlack ||
10347              gameMode == TwoMachinesPlay ||
10348              gameMode == IcsPlayingWhite ||
10349              gameMode == IcsPlayingBlack ||
10350              gameMode == BeginningOfGame)) {
10351             SendToProgram("force\n", &first);
10352             if (first.usePing) {
10353               char buf[MSG_SIZ];
10354               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10355               SendToProgram(buf, &first);
10356             }
10357         }
10358     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10359         /* Kill off first chess program */
10360         if (first.isr != NULL)
10361           RemoveInputSource(first.isr);
10362         first.isr = NULL;
10363
10364         if (first.pr != NoProc) {
10365             ExitAnalyzeMode();
10366             DoSleep( appData.delayBeforeQuit );
10367             SendToProgram("quit\n", &first);
10368             DoSleep( appData.delayAfterQuit );
10369             DestroyChildProcess(first.pr, first.useSigterm);
10370         }
10371         first.pr = NoProc;
10372     }
10373     if (second.reuse) {
10374         /* Put second chess program into idle state */
10375         if (second.pr != NoProc &&
10376             gameMode == TwoMachinesPlay) {
10377             SendToProgram("force\n", &second);
10378             if (second.usePing) {
10379               char buf[MSG_SIZ];
10380               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10381               SendToProgram(buf, &second);
10382             }
10383         }
10384     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10385         /* Kill off second chess program */
10386         if (second.isr != NULL)
10387           RemoveInputSource(second.isr);
10388         second.isr = NULL;
10389
10390         if (second.pr != NoProc) {
10391             DoSleep( appData.delayBeforeQuit );
10392             SendToProgram("quit\n", &second);
10393             DoSleep( appData.delayAfterQuit );
10394             DestroyChildProcess(second.pr, second.useSigterm);
10395         }
10396         second.pr = NoProc;
10397     }
10398
10399     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10400         char resChar = '=';
10401         switch (result) {
10402         case WhiteWins:
10403           resChar = '+';
10404           if (first.twoMachinesColor[0] == 'w') {
10405             first.matchWins++;
10406           } else {
10407             second.matchWins++;
10408           }
10409           break;
10410         case BlackWins:
10411           resChar = '-';
10412           if (first.twoMachinesColor[0] == 'b') {
10413             first.matchWins++;
10414           } else {
10415             second.matchWins++;
10416           }
10417           break;
10418         case GameUnfinished:
10419           resChar = ' ';
10420         default:
10421           break;
10422         }
10423
10424         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10425         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10426             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10427             ReserveGame(nextGame, resChar); // sets nextGame
10428             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10429             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10430         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10431
10432         if (nextGame <= appData.matchGames && !abortMatch) {
10433             gameMode = nextGameMode;
10434             matchGame = nextGame; // this will be overruled in tourney mode!
10435             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10436             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10437             endingGame = 0; /* [HGM] crash */
10438             return;
10439         } else {
10440             gameMode = nextGameMode;
10441             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10442                      first.tidy, second.tidy,
10443                      first.matchWins, second.matchWins,
10444                      appData.matchGames - (first.matchWins + second.matchWins));
10445             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10446             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10447             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10448             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10449                 first.twoMachinesColor = "black\n";
10450                 second.twoMachinesColor = "white\n";
10451             } else {
10452                 first.twoMachinesColor = "white\n";
10453                 second.twoMachinesColor = "black\n";
10454             }
10455         }
10456     }
10457     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10458         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10459       ExitAnalyzeMode();
10460     gameMode = nextGameMode;
10461     ModeHighlight();
10462     endingGame = 0;  /* [HGM] crash */
10463     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10464         if(matchMode == TRUE) { // match through command line: exit with or without popup
10465             if(ranking) {
10466                 ToNrEvent(forwardMostMove);
10467                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10468                 else ExitEvent(0);
10469             } else DisplayFatalError(buf, 0, 0);
10470         } else { // match through menu; just stop, with or without popup
10471             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10472             ModeHighlight();
10473             if(ranking){
10474                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10475             } else DisplayNote(buf);
10476       }
10477       if(ranking) free(ranking);
10478     }
10479 }
10480
10481 /* Assumes program was just initialized (initString sent).
10482    Leaves program in force mode. */
10483 void
10484 FeedMovesToProgram (ChessProgramState *cps, int upto)
10485 {
10486     int i;
10487
10488     if (appData.debugMode)
10489       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10490               startedFromSetupPosition ? "position and " : "",
10491               backwardMostMove, upto, cps->which);
10492     if(currentlyInitializedVariant != gameInfo.variant) {
10493       char buf[MSG_SIZ];
10494         // [HGM] variantswitch: make engine aware of new variant
10495         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10496                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10497         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10498         SendToProgram(buf, cps);
10499         currentlyInitializedVariant = gameInfo.variant;
10500     }
10501     SendToProgram("force\n", cps);
10502     if (startedFromSetupPosition) {
10503         SendBoard(cps, backwardMostMove);
10504     if (appData.debugMode) {
10505         fprintf(debugFP, "feedMoves\n");
10506     }
10507     }
10508     for (i = backwardMostMove; i < upto; i++) {
10509         SendMoveToProgram(i, cps);
10510     }
10511 }
10512
10513
10514 int
10515 ResurrectChessProgram ()
10516 {
10517      /* The chess program may have exited.
10518         If so, restart it and feed it all the moves made so far. */
10519     static int doInit = 0;
10520
10521     if (appData.noChessProgram) return 1;
10522
10523     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10524         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10525         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10526         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10527     } else {
10528         if (first.pr != NoProc) return 1;
10529         StartChessProgram(&first);
10530     }
10531     InitChessProgram(&first, FALSE);
10532     FeedMovesToProgram(&first, currentMove);
10533
10534     if (!first.sendTime) {
10535         /* can't tell gnuchess what its clock should read,
10536            so we bow to its notion. */
10537         ResetClocks();
10538         timeRemaining[0][currentMove] = whiteTimeRemaining;
10539         timeRemaining[1][currentMove] = blackTimeRemaining;
10540     }
10541
10542     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10543                 appData.icsEngineAnalyze) && first.analysisSupport) {
10544       SendToProgram("analyze\n", &first);
10545       first.analyzing = TRUE;
10546     }
10547     return 1;
10548 }
10549
10550 /*
10551  * Button procedures
10552  */
10553 void
10554 Reset (int redraw, int init)
10555 {
10556     int i;
10557
10558     if (appData.debugMode) {
10559         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10560                 redraw, init, gameMode);
10561     }
10562     CleanupTail(); // [HGM] vari: delete any stored variations
10563     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10564     pausing = pauseExamInvalid = FALSE;
10565     startedFromSetupPosition = blackPlaysFirst = FALSE;
10566     firstMove = TRUE;
10567     whiteFlag = blackFlag = FALSE;
10568     userOfferedDraw = FALSE;
10569     hintRequested = bookRequested = FALSE;
10570     first.maybeThinking = FALSE;
10571     second.maybeThinking = FALSE;
10572     first.bookSuspend = FALSE; // [HGM] book
10573     second.bookSuspend = FALSE;
10574     thinkOutput[0] = NULLCHAR;
10575     lastHint[0] = NULLCHAR;
10576     ClearGameInfo(&gameInfo);
10577     gameInfo.variant = StringToVariant(appData.variant);
10578     ics_user_moved = ics_clock_paused = FALSE;
10579     ics_getting_history = H_FALSE;
10580     ics_gamenum = -1;
10581     white_holding[0] = black_holding[0] = NULLCHAR;
10582     ClearProgramStats();
10583     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10584
10585     ResetFrontEnd();
10586     ClearHighlights();
10587     flipView = appData.flipView;
10588     ClearPremoveHighlights();
10589     gotPremove = FALSE;
10590     alarmSounded = FALSE;
10591
10592     GameEnds(EndOfFile, NULL, GE_PLAYER);
10593     if(appData.serverMovesName != NULL) {
10594         /* [HGM] prepare to make moves file for broadcasting */
10595         clock_t t = clock();
10596         if(serverMoves != NULL) fclose(serverMoves);
10597         serverMoves = fopen(appData.serverMovesName, "r");
10598         if(serverMoves != NULL) {
10599             fclose(serverMoves);
10600             /* delay 15 sec before overwriting, so all clients can see end */
10601             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10602         }
10603         serverMoves = fopen(appData.serverMovesName, "w");
10604     }
10605
10606     ExitAnalyzeMode();
10607     gameMode = BeginningOfGame;
10608     ModeHighlight();
10609     if(appData.icsActive) gameInfo.variant = VariantNormal;
10610     currentMove = forwardMostMove = backwardMostMove = 0;
10611     MarkTargetSquares(1);
10612     InitPosition(redraw);
10613     for (i = 0; i < MAX_MOVES; i++) {
10614         if (commentList[i] != NULL) {
10615             free(commentList[i]);
10616             commentList[i] = NULL;
10617         }
10618     }
10619     ResetClocks();
10620     timeRemaining[0][0] = whiteTimeRemaining;
10621     timeRemaining[1][0] = blackTimeRemaining;
10622
10623     if (first.pr == NoProc) {
10624         StartChessProgram(&first);
10625     }
10626     if (init) {
10627             InitChessProgram(&first, startedFromSetupPosition);
10628     }
10629     DisplayTitle("");
10630     DisplayMessage("", "");
10631     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10632     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10633 }
10634
10635 void
10636 AutoPlayGameLoop ()
10637 {
10638     for (;;) {
10639         if (!AutoPlayOneMove())
10640           return;
10641         if (matchMode || appData.timeDelay == 0)
10642           continue;
10643         if (appData.timeDelay < 0)
10644           return;
10645         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10646         break;
10647     }
10648 }
10649
10650
10651 int
10652 AutoPlayOneMove ()
10653 {
10654     int fromX, fromY, toX, toY;
10655
10656     if (appData.debugMode) {
10657       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10658     }
10659
10660     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10661       return FALSE;
10662
10663     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10664       pvInfoList[currentMove].depth = programStats.depth;
10665       pvInfoList[currentMove].score = programStats.score;
10666       pvInfoList[currentMove].time  = 0;
10667       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10668     }
10669
10670     if (currentMove >= forwardMostMove) {
10671       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10672 //      gameMode = EndOfGame;
10673 //      ModeHighlight();
10674
10675       /* [AS] Clear current move marker at the end of a game */
10676       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10677
10678       return FALSE;
10679     }
10680
10681     toX = moveList[currentMove][2] - AAA;
10682     toY = moveList[currentMove][3] - ONE;
10683
10684     if (moveList[currentMove][1] == '@') {
10685         if (appData.highlightLastMove) {
10686             SetHighlights(-1, -1, toX, toY);
10687         }
10688     } else {
10689         fromX = moveList[currentMove][0] - AAA;
10690         fromY = moveList[currentMove][1] - ONE;
10691
10692         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10693
10694         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10695
10696         if (appData.highlightLastMove) {
10697             SetHighlights(fromX, fromY, toX, toY);
10698         }
10699     }
10700     DisplayMove(currentMove);
10701     SendMoveToProgram(currentMove++, &first);
10702     DisplayBothClocks();
10703     DrawPosition(FALSE, boards[currentMove]);
10704     // [HGM] PV info: always display, routine tests if empty
10705     DisplayComment(currentMove - 1, commentList[currentMove]);
10706     return TRUE;
10707 }
10708
10709
10710 int
10711 LoadGameOneMove (ChessMove readAhead)
10712 {
10713     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10714     char promoChar = NULLCHAR;
10715     ChessMove moveType;
10716     char move[MSG_SIZ];
10717     char *p, *q;
10718
10719     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10720         gameMode != AnalyzeMode && gameMode != Training) {
10721         gameFileFP = NULL;
10722         return FALSE;
10723     }
10724
10725     yyboardindex = forwardMostMove;
10726     if (readAhead != EndOfFile) {
10727       moveType = readAhead;
10728     } else {
10729       if (gameFileFP == NULL)
10730           return FALSE;
10731       moveType = (ChessMove) Myylex();
10732     }
10733
10734     done = FALSE;
10735     switch (moveType) {
10736       case Comment:
10737         if (appData.debugMode)
10738           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10739         p = yy_text;
10740
10741         /* append the comment but don't display it */
10742         AppendComment(currentMove, p, FALSE);
10743         return TRUE;
10744
10745       case WhiteCapturesEnPassant:
10746       case BlackCapturesEnPassant:
10747       case WhitePromotion:
10748       case BlackPromotion:
10749       case WhiteNonPromotion:
10750       case BlackNonPromotion:
10751       case NormalMove:
10752       case WhiteKingSideCastle:
10753       case WhiteQueenSideCastle:
10754       case BlackKingSideCastle:
10755       case BlackQueenSideCastle:
10756       case WhiteKingSideCastleWild:
10757       case WhiteQueenSideCastleWild:
10758       case BlackKingSideCastleWild:
10759       case BlackQueenSideCastleWild:
10760       /* PUSH Fabien */
10761       case WhiteHSideCastleFR:
10762       case WhiteASideCastleFR:
10763       case BlackHSideCastleFR:
10764       case BlackASideCastleFR:
10765       /* POP Fabien */
10766         if (appData.debugMode)
10767           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10768         fromX = currentMoveString[0] - AAA;
10769         fromY = currentMoveString[1] - ONE;
10770         toX = currentMoveString[2] - AAA;
10771         toY = currentMoveString[3] - ONE;
10772         promoChar = currentMoveString[4];
10773         break;
10774
10775       case WhiteDrop:
10776       case BlackDrop:
10777         if (appData.debugMode)
10778           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10779         fromX = moveType == WhiteDrop ?
10780           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10781         (int) CharToPiece(ToLower(currentMoveString[0]));
10782         fromY = DROP_RANK;
10783         toX = currentMoveString[2] - AAA;
10784         toY = currentMoveString[3] - ONE;
10785         break;
10786
10787       case WhiteWins:
10788       case BlackWins:
10789       case GameIsDrawn:
10790       case GameUnfinished:
10791         if (appData.debugMode)
10792           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10793         p = strchr(yy_text, '{');
10794         if (p == NULL) p = strchr(yy_text, '(');
10795         if (p == NULL) {
10796             p = yy_text;
10797             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10798         } else {
10799             q = strchr(p, *p == '{' ? '}' : ')');
10800             if (q != NULL) *q = NULLCHAR;
10801             p++;
10802         }
10803         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10804         GameEnds(moveType, p, GE_FILE);
10805         done = TRUE;
10806         if (cmailMsgLoaded) {
10807             ClearHighlights();
10808             flipView = WhiteOnMove(currentMove);
10809             if (moveType == GameUnfinished) flipView = !flipView;
10810             if (appData.debugMode)
10811               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10812         }
10813         break;
10814
10815       case EndOfFile:
10816         if (appData.debugMode)
10817           fprintf(debugFP, "Parser hit end of file\n");
10818         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10819           case MT_NONE:
10820           case MT_CHECK:
10821             break;
10822           case MT_CHECKMATE:
10823           case MT_STAINMATE:
10824             if (WhiteOnMove(currentMove)) {
10825                 GameEnds(BlackWins, "Black mates", GE_FILE);
10826             } else {
10827                 GameEnds(WhiteWins, "White mates", GE_FILE);
10828             }
10829             break;
10830           case MT_STALEMATE:
10831             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10832             break;
10833         }
10834         done = TRUE;
10835         break;
10836
10837       case MoveNumberOne:
10838         if (lastLoadGameStart == GNUChessGame) {
10839             /* GNUChessGames have numbers, but they aren't move numbers */
10840             if (appData.debugMode)
10841               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10842                       yy_text, (int) moveType);
10843             return LoadGameOneMove(EndOfFile); /* tail recursion */
10844         }
10845         /* else fall thru */
10846
10847       case XBoardGame:
10848       case GNUChessGame:
10849       case PGNTag:
10850         /* Reached start of next game in file */
10851         if (appData.debugMode)
10852           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10853         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10854           case MT_NONE:
10855           case MT_CHECK:
10856             break;
10857           case MT_CHECKMATE:
10858           case MT_STAINMATE:
10859             if (WhiteOnMove(currentMove)) {
10860                 GameEnds(BlackWins, "Black mates", GE_FILE);
10861             } else {
10862                 GameEnds(WhiteWins, "White mates", GE_FILE);
10863             }
10864             break;
10865           case MT_STALEMATE:
10866             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10867             break;
10868         }
10869         done = TRUE;
10870         break;
10871
10872       case PositionDiagram:     /* should not happen; ignore */
10873       case ElapsedTime:         /* ignore */
10874       case NAG:                 /* ignore */
10875         if (appData.debugMode)
10876           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10877                   yy_text, (int) moveType);
10878         return LoadGameOneMove(EndOfFile); /* tail recursion */
10879
10880       case IllegalMove:
10881         if (appData.testLegality) {
10882             if (appData.debugMode)
10883               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10884             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10885                     (forwardMostMove / 2) + 1,
10886                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10887             DisplayError(move, 0);
10888             done = TRUE;
10889         } else {
10890             if (appData.debugMode)
10891               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10892                       yy_text, currentMoveString);
10893             fromX = currentMoveString[0] - AAA;
10894             fromY = currentMoveString[1] - ONE;
10895             toX = currentMoveString[2] - AAA;
10896             toY = currentMoveString[3] - ONE;
10897             promoChar = currentMoveString[4];
10898         }
10899         break;
10900
10901       case AmbiguousMove:
10902         if (appData.debugMode)
10903           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10904         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10905                 (forwardMostMove / 2) + 1,
10906                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10907         DisplayError(move, 0);
10908         done = TRUE;
10909         break;
10910
10911       default:
10912       case ImpossibleMove:
10913         if (appData.debugMode)
10914           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10915         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10916                 (forwardMostMove / 2) + 1,
10917                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10918         DisplayError(move, 0);
10919         done = TRUE;
10920         break;
10921     }
10922
10923     if (done) {
10924         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10925             DrawPosition(FALSE, boards[currentMove]);
10926             DisplayBothClocks();
10927             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10928               DisplayComment(currentMove - 1, commentList[currentMove]);
10929         }
10930         (void) StopLoadGameTimer();
10931         gameFileFP = NULL;
10932         cmailOldMove = forwardMostMove;
10933         return FALSE;
10934     } else {
10935         /* currentMoveString is set as a side-effect of yylex */
10936
10937         thinkOutput[0] = NULLCHAR;
10938         MakeMove(fromX, fromY, toX, toY, promoChar);
10939         currentMove = forwardMostMove;
10940         return TRUE;
10941     }
10942 }
10943
10944 /* Load the nth game from the given file */
10945 int
10946 LoadGameFromFile (char *filename, int n, char *title, int useList)
10947 {
10948     FILE *f;
10949     char buf[MSG_SIZ];
10950
10951     if (strcmp(filename, "-") == 0) {
10952         f = stdin;
10953         title = "stdin";
10954     } else {
10955         f = fopen(filename, "rb");
10956         if (f == NULL) {
10957           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10958             DisplayError(buf, errno);
10959             return FALSE;
10960         }
10961     }
10962     if (fseek(f, 0, 0) == -1) {
10963         /* f is not seekable; probably a pipe */
10964         useList = FALSE;
10965     }
10966     if (useList && n == 0) {
10967         int error = GameListBuild(f);
10968         if (error) {
10969             DisplayError(_("Cannot build game list"), error);
10970         } else if (!ListEmpty(&gameList) &&
10971                    ((ListGame *) gameList.tailPred)->number > 1) {
10972             GameListPopUp(f, title);
10973             return TRUE;
10974         }
10975         GameListDestroy();
10976         n = 1;
10977     }
10978     if (n == 0) n = 1;
10979     return LoadGame(f, n, title, FALSE);
10980 }
10981
10982
10983 void
10984 MakeRegisteredMove ()
10985 {
10986     int fromX, fromY, toX, toY;
10987     char promoChar;
10988     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10989         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10990           case CMAIL_MOVE:
10991           case CMAIL_DRAW:
10992             if (appData.debugMode)
10993               fprintf(debugFP, "Restoring %s for game %d\n",
10994                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10995
10996             thinkOutput[0] = NULLCHAR;
10997             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10998             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10999             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11000             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11001             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11002             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11003             MakeMove(fromX, fromY, toX, toY, promoChar);
11004             ShowMove(fromX, fromY, toX, toY);
11005
11006             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11007               case MT_NONE:
11008               case MT_CHECK:
11009                 break;
11010
11011               case MT_CHECKMATE:
11012               case MT_STAINMATE:
11013                 if (WhiteOnMove(currentMove)) {
11014                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11015                 } else {
11016                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11017                 }
11018                 break;
11019
11020               case MT_STALEMATE:
11021                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11022                 break;
11023             }
11024
11025             break;
11026
11027           case CMAIL_RESIGN:
11028             if (WhiteOnMove(currentMove)) {
11029                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11030             } else {
11031                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11032             }
11033             break;
11034
11035           case CMAIL_ACCEPT:
11036             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11037             break;
11038
11039           default:
11040             break;
11041         }
11042     }
11043
11044     return;
11045 }
11046
11047 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11048 int
11049 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11050 {
11051     int retVal;
11052
11053     if (gameNumber > nCmailGames) {
11054         DisplayError(_("No more games in this message"), 0);
11055         return FALSE;
11056     }
11057     if (f == lastLoadGameFP) {
11058         int offset = gameNumber - lastLoadGameNumber;
11059         if (offset == 0) {
11060             cmailMsg[0] = NULLCHAR;
11061             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11062                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11063                 nCmailMovesRegistered--;
11064             }
11065             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11066             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11067                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11068             }
11069         } else {
11070             if (! RegisterMove()) return FALSE;
11071         }
11072     }
11073
11074     retVal = LoadGame(f, gameNumber, title, useList);
11075
11076     /* Make move registered during previous look at this game, if any */
11077     MakeRegisteredMove();
11078
11079     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11080         commentList[currentMove]
11081           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11082         DisplayComment(currentMove - 1, commentList[currentMove]);
11083     }
11084
11085     return retVal;
11086 }
11087
11088 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11089 int
11090 ReloadGame (int offset)
11091 {
11092     int gameNumber = lastLoadGameNumber + offset;
11093     if (lastLoadGameFP == NULL) {
11094         DisplayError(_("No game has been loaded yet"), 0);
11095         return FALSE;
11096     }
11097     if (gameNumber <= 0) {
11098         DisplayError(_("Can't back up any further"), 0);
11099         return FALSE;
11100     }
11101     if (cmailMsgLoaded) {
11102         return CmailLoadGame(lastLoadGameFP, gameNumber,
11103                              lastLoadGameTitle, lastLoadGameUseList);
11104     } else {
11105         return LoadGame(lastLoadGameFP, gameNumber,
11106                         lastLoadGameTitle, lastLoadGameUseList);
11107     }
11108 }
11109
11110 int keys[EmptySquare+1];
11111
11112 int
11113 PositionMatches (Board b1, Board b2)
11114 {
11115     int r, f, sum=0;
11116     switch(appData.searchMode) {
11117         case 1: return CompareWithRights(b1, b2);
11118         case 2:
11119             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11120                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11121             }
11122             return TRUE;
11123         case 3:
11124             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11125               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11126                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11127             }
11128             return sum==0;
11129         case 4:
11130             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11131                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11132             }
11133             return sum==0;
11134     }
11135     return TRUE;
11136 }
11137
11138 #define Q_PROMO  4
11139 #define Q_EP     3
11140 #define Q_BCASTL 2
11141 #define Q_WCASTL 1
11142
11143 int pieceList[256], quickBoard[256];
11144 ChessSquare pieceType[256] = { EmptySquare };
11145 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11146 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11147 int soughtTotal, turn;
11148 Boolean epOK, flipSearch;
11149
11150 typedef struct {
11151     unsigned char piece, to;
11152 } Move;
11153
11154 #define DSIZE (250000)
11155
11156 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11157 Move *moveDatabase = initialSpace;
11158 unsigned int movePtr, dataSize = DSIZE;
11159
11160 int
11161 MakePieceList (Board board, int *counts)
11162 {
11163     int r, f, n=Q_PROMO, total=0;
11164     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11165     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11166         int sq = f + (r<<4);
11167         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11168             quickBoard[sq] = ++n;
11169             pieceList[n] = sq;
11170             pieceType[n] = board[r][f];
11171             counts[board[r][f]]++;
11172             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11173             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11174             total++;
11175         }
11176     }
11177     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11178     return total;
11179 }
11180
11181 void
11182 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11183 {
11184     int sq = fromX + (fromY<<4);
11185     int piece = quickBoard[sq];
11186     quickBoard[sq] = 0;
11187     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11188     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11189         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11190         moveDatabase[movePtr++].piece = Q_WCASTL;
11191         quickBoard[sq] = piece;
11192         piece = quickBoard[from]; quickBoard[from] = 0;
11193         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11194     } else
11195     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11196         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11197         moveDatabase[movePtr++].piece = Q_BCASTL;
11198         quickBoard[sq] = piece;
11199         piece = quickBoard[from]; quickBoard[from] = 0;
11200         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11201     } else
11202     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11203         quickBoard[(fromY<<4)+toX] = 0;
11204         moveDatabase[movePtr].piece = Q_EP;
11205         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11206         moveDatabase[movePtr].to = sq;
11207     } else
11208     if(promoPiece != pieceType[piece]) {
11209         moveDatabase[movePtr++].piece = Q_PROMO;
11210         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11211     }
11212     moveDatabase[movePtr].piece = piece;
11213     quickBoard[sq] = piece;
11214     movePtr++;
11215 }
11216
11217 int
11218 PackGame (Board board)
11219 {
11220     Move *newSpace = NULL;
11221     moveDatabase[movePtr].piece = 0; // terminate previous game
11222     if(movePtr > dataSize) {
11223         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11224         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11225         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11226         if(newSpace) {
11227             int i;
11228             Move *p = moveDatabase, *q = newSpace;
11229             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11230             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11231             moveDatabase = newSpace;
11232         } else { // calloc failed, we must be out of memory. Too bad...
11233             dataSize = 0; // prevent calloc events for all subsequent games
11234             return 0;     // and signal this one isn't cached
11235         }
11236     }
11237     movePtr++;
11238     MakePieceList(board, counts);
11239     return movePtr;
11240 }
11241
11242 int
11243 QuickCompare (Board board, int *minCounts, int *maxCounts)
11244 {   // compare according to search mode
11245     int r, f;
11246     switch(appData.searchMode)
11247     {
11248       case 1: // exact position match
11249         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11250         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11251             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11252         }
11253         break;
11254       case 2: // can have extra material on empty squares
11255         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11256             if(board[r][f] == EmptySquare) continue;
11257             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11258         }
11259         break;
11260       case 3: // material with exact Pawn structure
11261         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11262             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11263             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11264         } // fall through to material comparison
11265       case 4: // exact material
11266         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11267         break;
11268       case 6: // material range with given imbalance
11269         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11270         // fall through to range comparison
11271       case 5: // material range
11272         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11273     }
11274     return TRUE;
11275 }
11276
11277 int
11278 QuickScan (Board board, Move *move)
11279 {   // reconstruct game,and compare all positions in it
11280     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11281     do {
11282         int piece = move->piece;
11283         int to = move->to, from = pieceList[piece];
11284         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11285           if(!piece) return -1;
11286           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11287             piece = (++move)->piece;
11288             from = pieceList[piece];
11289             counts[pieceType[piece]]--;
11290             pieceType[piece] = (ChessSquare) move->to;
11291             counts[move->to]++;
11292           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11293             counts[pieceType[quickBoard[to]]]--;
11294             quickBoard[to] = 0; total--;
11295             move++;
11296             continue;
11297           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11298             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11299             from  = pieceList[piece]; // so this must be King
11300             quickBoard[from] = 0;
11301             quickBoard[to] = piece;
11302             pieceList[piece] = to;
11303             move++;
11304             continue;
11305           }
11306         }
11307         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11308         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11309         quickBoard[from] = 0;
11310         quickBoard[to] = piece;
11311         pieceList[piece] = to;
11312         cnt++; turn ^= 3;
11313         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11314            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11315            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11316                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11317           ) {
11318             static int lastCounts[EmptySquare+1];
11319             int i;
11320             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11321             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11322         } else stretch = 0;
11323         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11324         move++;
11325     } while(1);
11326 }
11327
11328 void
11329 InitSearch ()
11330 {
11331     int r, f;
11332     flipSearch = FALSE;
11333     CopyBoard(soughtBoard, boards[currentMove]);
11334     soughtTotal = MakePieceList(soughtBoard, maxSought);
11335     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11336     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11337     CopyBoard(reverseBoard, boards[currentMove]);
11338     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11339         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11340         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11341         reverseBoard[r][f] = piece;
11342     }
11343     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11344     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11345     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11346                  || (boards[currentMove][CASTLING][2] == NoRights || 
11347                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11348                  && (boards[currentMove][CASTLING][5] == NoRights || 
11349                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11350       ) {
11351         flipSearch = TRUE;
11352         CopyBoard(flipBoard, soughtBoard);
11353         CopyBoard(rotateBoard, reverseBoard);
11354         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11355             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11356             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11357         }
11358     }
11359     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11360     if(appData.searchMode >= 5) {
11361         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11362         MakePieceList(soughtBoard, minSought);
11363         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11364     }
11365     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11366         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11367 }
11368
11369 GameInfo dummyInfo;
11370
11371 int
11372 GameContainsPosition (FILE *f, ListGame *lg)
11373 {
11374     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11375     int fromX, fromY, toX, toY;
11376     char promoChar;
11377     static int initDone=FALSE;
11378
11379     // weed out games based on numerical tag comparison
11380     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11381     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11382     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11383     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11384     if(!initDone) {
11385         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11386         initDone = TRUE;
11387     }
11388     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11389     else CopyBoard(boards[scratch], initialPosition); // default start position
11390     if(lg->moves) {
11391         turn = btm + 1;
11392         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11393         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11394     }
11395     if(btm) plyNr++;
11396     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11397     fseek(f, lg->offset, 0);
11398     yynewfile(f);
11399     while(1) {
11400         yyboardindex = scratch;
11401         quickFlag = plyNr+1;
11402         next = Myylex();
11403         quickFlag = 0;
11404         switch(next) {
11405             case PGNTag:
11406                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11407             default:
11408                 continue;
11409
11410             case XBoardGame:
11411             case GNUChessGame:
11412                 if(plyNr) return -1; // after we have seen moves, this is for new game
11413               continue;
11414
11415             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11416             case ImpossibleMove:
11417             case WhiteWins: // game ends here with these four
11418             case BlackWins:
11419             case GameIsDrawn:
11420             case GameUnfinished:
11421                 return -1;
11422
11423             case IllegalMove:
11424                 if(appData.testLegality) return -1;
11425             case WhiteCapturesEnPassant:
11426             case BlackCapturesEnPassant:
11427             case WhitePromotion:
11428             case BlackPromotion:
11429             case WhiteNonPromotion:
11430             case BlackNonPromotion:
11431             case NormalMove:
11432             case WhiteKingSideCastle:
11433             case WhiteQueenSideCastle:
11434             case BlackKingSideCastle:
11435             case BlackQueenSideCastle:
11436             case WhiteKingSideCastleWild:
11437             case WhiteQueenSideCastleWild:
11438             case BlackKingSideCastleWild:
11439             case BlackQueenSideCastleWild:
11440             case WhiteHSideCastleFR:
11441             case WhiteASideCastleFR:
11442             case BlackHSideCastleFR:
11443             case BlackASideCastleFR:
11444                 fromX = currentMoveString[0] - AAA;
11445                 fromY = currentMoveString[1] - ONE;
11446                 toX = currentMoveString[2] - AAA;
11447                 toY = currentMoveString[3] - ONE;
11448                 promoChar = currentMoveString[4];
11449                 break;
11450             case WhiteDrop:
11451             case BlackDrop:
11452                 fromX = next == WhiteDrop ?
11453                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11454                   (int) CharToPiece(ToLower(currentMoveString[0]));
11455                 fromY = DROP_RANK;
11456                 toX = currentMoveString[2] - AAA;
11457                 toY = currentMoveString[3] - ONE;
11458                 promoChar = 0;
11459                 break;
11460         }
11461         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11462         plyNr++;
11463         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11464         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11465         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11466         if(appData.findMirror) {
11467             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11468             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11469         }
11470     }
11471 }
11472
11473 /* Load the nth game from open file f */
11474 int
11475 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11476 {
11477     ChessMove cm;
11478     char buf[MSG_SIZ];
11479     int gn = gameNumber;
11480     ListGame *lg = NULL;
11481     int numPGNTags = 0;
11482     int err, pos = -1;
11483     GameMode oldGameMode;
11484     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11485
11486     if (appData.debugMode)
11487         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11488
11489     if (gameMode == Training )
11490         SetTrainingModeOff();
11491
11492     oldGameMode = gameMode;
11493     if (gameMode != BeginningOfGame) {
11494       Reset(FALSE, TRUE);
11495     }
11496
11497     gameFileFP = f;
11498     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11499         fclose(lastLoadGameFP);
11500     }
11501
11502     if (useList) {
11503         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11504
11505         if (lg) {
11506             fseek(f, lg->offset, 0);
11507             GameListHighlight(gameNumber);
11508             pos = lg->position;
11509             gn = 1;
11510         }
11511         else {
11512             DisplayError(_("Game number out of range"), 0);
11513             return FALSE;
11514         }
11515     } else {
11516         GameListDestroy();
11517         if (fseek(f, 0, 0) == -1) {
11518             if (f == lastLoadGameFP ?
11519                 gameNumber == lastLoadGameNumber + 1 :
11520                 gameNumber == 1) {
11521                 gn = 1;
11522             } else {
11523                 DisplayError(_("Can't seek on game file"), 0);
11524                 return FALSE;
11525             }
11526         }
11527     }
11528     lastLoadGameFP = f;
11529     lastLoadGameNumber = gameNumber;
11530     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11531     lastLoadGameUseList = useList;
11532
11533     yynewfile(f);
11534
11535     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11536       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11537                 lg->gameInfo.black);
11538             DisplayTitle(buf);
11539     } else if (*title != NULLCHAR) {
11540         if (gameNumber > 1) {
11541           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11542             DisplayTitle(buf);
11543         } else {
11544             DisplayTitle(title);
11545         }
11546     }
11547
11548     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11549         gameMode = PlayFromGameFile;
11550         ModeHighlight();
11551     }
11552
11553     currentMove = forwardMostMove = backwardMostMove = 0;
11554     CopyBoard(boards[0], initialPosition);
11555     StopClocks();
11556
11557     /*
11558      * Skip the first gn-1 games in the file.
11559      * Also skip over anything that precedes an identifiable
11560      * start of game marker, to avoid being confused by
11561      * garbage at the start of the file.  Currently
11562      * recognized start of game markers are the move number "1",
11563      * the pattern "gnuchess .* game", the pattern
11564      * "^[#;%] [^ ]* game file", and a PGN tag block.
11565      * A game that starts with one of the latter two patterns
11566      * will also have a move number 1, possibly
11567      * following a position diagram.
11568      * 5-4-02: Let's try being more lenient and allowing a game to
11569      * start with an unnumbered move.  Does that break anything?
11570      */
11571     cm = lastLoadGameStart = EndOfFile;
11572     while (gn > 0) {
11573         yyboardindex = forwardMostMove;
11574         cm = (ChessMove) Myylex();
11575         switch (cm) {
11576           case EndOfFile:
11577             if (cmailMsgLoaded) {
11578                 nCmailGames = CMAIL_MAX_GAMES - gn;
11579             } else {
11580                 Reset(TRUE, TRUE);
11581                 DisplayError(_("Game not found in file"), 0);
11582             }
11583             return FALSE;
11584
11585           case GNUChessGame:
11586           case XBoardGame:
11587             gn--;
11588             lastLoadGameStart = cm;
11589             break;
11590
11591           case MoveNumberOne:
11592             switch (lastLoadGameStart) {
11593               case GNUChessGame:
11594               case XBoardGame:
11595               case PGNTag:
11596                 break;
11597               case MoveNumberOne:
11598               case EndOfFile:
11599                 gn--;           /* count this game */
11600                 lastLoadGameStart = cm;
11601                 break;
11602               default:
11603                 /* impossible */
11604                 break;
11605             }
11606             break;
11607
11608           case PGNTag:
11609             switch (lastLoadGameStart) {
11610               case GNUChessGame:
11611               case PGNTag:
11612               case MoveNumberOne:
11613               case EndOfFile:
11614                 gn--;           /* count this game */
11615                 lastLoadGameStart = cm;
11616                 break;
11617               case XBoardGame:
11618                 lastLoadGameStart = cm; /* game counted already */
11619                 break;
11620               default:
11621                 /* impossible */
11622                 break;
11623             }
11624             if (gn > 0) {
11625                 do {
11626                     yyboardindex = forwardMostMove;
11627                     cm = (ChessMove) Myylex();
11628                 } while (cm == PGNTag || cm == Comment);
11629             }
11630             break;
11631
11632           case WhiteWins:
11633           case BlackWins:
11634           case GameIsDrawn:
11635             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11636                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11637                     != CMAIL_OLD_RESULT) {
11638                     nCmailResults ++ ;
11639                     cmailResult[  CMAIL_MAX_GAMES
11640                                 - gn - 1] = CMAIL_OLD_RESULT;
11641                 }
11642             }
11643             break;
11644
11645           case NormalMove:
11646             /* Only a NormalMove can be at the start of a game
11647              * without a position diagram. */
11648             if (lastLoadGameStart == EndOfFile ) {
11649               gn--;
11650               lastLoadGameStart = MoveNumberOne;
11651             }
11652             break;
11653
11654           default:
11655             break;
11656         }
11657     }
11658
11659     if (appData.debugMode)
11660       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11661
11662     if (cm == XBoardGame) {
11663         /* Skip any header junk before position diagram and/or move 1 */
11664         for (;;) {
11665             yyboardindex = forwardMostMove;
11666             cm = (ChessMove) Myylex();
11667
11668             if (cm == EndOfFile ||
11669                 cm == GNUChessGame || cm == XBoardGame) {
11670                 /* Empty game; pretend end-of-file and handle later */
11671                 cm = EndOfFile;
11672                 break;
11673             }
11674
11675             if (cm == MoveNumberOne || cm == PositionDiagram ||
11676                 cm == PGNTag || cm == Comment)
11677               break;
11678         }
11679     } else if (cm == GNUChessGame) {
11680         if (gameInfo.event != NULL) {
11681             free(gameInfo.event);
11682         }
11683         gameInfo.event = StrSave(yy_text);
11684     }
11685
11686     startedFromSetupPosition = FALSE;
11687     while (cm == PGNTag) {
11688         if (appData.debugMode)
11689           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11690         err = ParsePGNTag(yy_text, &gameInfo);
11691         if (!err) numPGNTags++;
11692
11693         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11694         if(gameInfo.variant != oldVariant) {
11695             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11696             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11697             InitPosition(TRUE);
11698             oldVariant = gameInfo.variant;
11699             if (appData.debugMode)
11700               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11701         }
11702
11703
11704         if (gameInfo.fen != NULL) {
11705           Board initial_position;
11706           startedFromSetupPosition = TRUE;
11707           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11708             Reset(TRUE, TRUE);
11709             DisplayError(_("Bad FEN position in file"), 0);
11710             return FALSE;
11711           }
11712           CopyBoard(boards[0], initial_position);
11713           if (blackPlaysFirst) {
11714             currentMove = forwardMostMove = backwardMostMove = 1;
11715             CopyBoard(boards[1], initial_position);
11716             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11717             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11718             timeRemaining[0][1] = whiteTimeRemaining;
11719             timeRemaining[1][1] = blackTimeRemaining;
11720             if (commentList[0] != NULL) {
11721               commentList[1] = commentList[0];
11722               commentList[0] = NULL;
11723             }
11724           } else {
11725             currentMove = forwardMostMove = backwardMostMove = 0;
11726           }
11727           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11728           {   int i;
11729               initialRulePlies = FENrulePlies;
11730               for( i=0; i< nrCastlingRights; i++ )
11731                   initialRights[i] = initial_position[CASTLING][i];
11732           }
11733           yyboardindex = forwardMostMove;
11734           free(gameInfo.fen);
11735           gameInfo.fen = NULL;
11736         }
11737
11738         yyboardindex = forwardMostMove;
11739         cm = (ChessMove) Myylex();
11740
11741         /* Handle comments interspersed among the tags */
11742         while (cm == Comment) {
11743             char *p;
11744             if (appData.debugMode)
11745               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11746             p = yy_text;
11747             AppendComment(currentMove, p, FALSE);
11748             yyboardindex = forwardMostMove;
11749             cm = (ChessMove) Myylex();
11750         }
11751     }
11752
11753     /* don't rely on existence of Event tag since if game was
11754      * pasted from clipboard the Event tag may not exist
11755      */
11756     if (numPGNTags > 0){
11757         char *tags;
11758         if (gameInfo.variant == VariantNormal) {
11759           VariantClass v = StringToVariant(gameInfo.event);
11760           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11761           if(v < VariantShogi) gameInfo.variant = v;
11762         }
11763         if (!matchMode) {
11764           if( appData.autoDisplayTags ) {
11765             tags = PGNTags(&gameInfo);
11766             TagsPopUp(tags, CmailMsg());
11767             free(tags);
11768           }
11769         }
11770     } else {
11771         /* Make something up, but don't display it now */
11772         SetGameInfo();
11773         TagsPopDown();
11774     }
11775
11776     if (cm == PositionDiagram) {
11777         int i, j;
11778         char *p;
11779         Board initial_position;
11780
11781         if (appData.debugMode)
11782           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11783
11784         if (!startedFromSetupPosition) {
11785             p = yy_text;
11786             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11787               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11788                 switch (*p) {
11789                   case '{':
11790                   case '[':
11791                   case '-':
11792                   case ' ':
11793                   case '\t':
11794                   case '\n':
11795                   case '\r':
11796                     break;
11797                   default:
11798                     initial_position[i][j++] = CharToPiece(*p);
11799                     break;
11800                 }
11801             while (*p == ' ' || *p == '\t' ||
11802                    *p == '\n' || *p == '\r') p++;
11803
11804             if (strncmp(p, "black", strlen("black"))==0)
11805               blackPlaysFirst = TRUE;
11806             else
11807               blackPlaysFirst = FALSE;
11808             startedFromSetupPosition = TRUE;
11809
11810             CopyBoard(boards[0], initial_position);
11811             if (blackPlaysFirst) {
11812                 currentMove = forwardMostMove = backwardMostMove = 1;
11813                 CopyBoard(boards[1], initial_position);
11814                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11815                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11816                 timeRemaining[0][1] = whiteTimeRemaining;
11817                 timeRemaining[1][1] = blackTimeRemaining;
11818                 if (commentList[0] != NULL) {
11819                     commentList[1] = commentList[0];
11820                     commentList[0] = NULL;
11821                 }
11822             } else {
11823                 currentMove = forwardMostMove = backwardMostMove = 0;
11824             }
11825         }
11826         yyboardindex = forwardMostMove;
11827         cm = (ChessMove) Myylex();
11828     }
11829
11830     if (first.pr == NoProc) {
11831         StartChessProgram(&first);
11832     }
11833     InitChessProgram(&first, FALSE);
11834     SendToProgram("force\n", &first);
11835     if (startedFromSetupPosition) {
11836         SendBoard(&first, forwardMostMove);
11837     if (appData.debugMode) {
11838         fprintf(debugFP, "Load Game\n");
11839     }
11840         DisplayBothClocks();
11841     }
11842
11843     /* [HGM] server: flag to write setup moves in broadcast file as one */
11844     loadFlag = appData.suppressLoadMoves;
11845
11846     while (cm == Comment) {
11847         char *p;
11848         if (appData.debugMode)
11849           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11850         p = yy_text;
11851         AppendComment(currentMove, p, FALSE);
11852         yyboardindex = forwardMostMove;
11853         cm = (ChessMove) Myylex();
11854     }
11855
11856     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11857         cm == WhiteWins || cm == BlackWins ||
11858         cm == GameIsDrawn || cm == GameUnfinished) {
11859         DisplayMessage("", _("No moves in game"));
11860         if (cmailMsgLoaded) {
11861             if (appData.debugMode)
11862               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11863             ClearHighlights();
11864             flipView = FALSE;
11865         }
11866         DrawPosition(FALSE, boards[currentMove]);
11867         DisplayBothClocks();
11868         gameMode = EditGame;
11869         ModeHighlight();
11870         gameFileFP = NULL;
11871         cmailOldMove = 0;
11872         return TRUE;
11873     }
11874
11875     // [HGM] PV info: routine tests if comment empty
11876     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11877         DisplayComment(currentMove - 1, commentList[currentMove]);
11878     }
11879     if (!matchMode && appData.timeDelay != 0)
11880       DrawPosition(FALSE, boards[currentMove]);
11881
11882     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11883       programStats.ok_to_send = 1;
11884     }
11885
11886     /* if the first token after the PGN tags is a move
11887      * and not move number 1, retrieve it from the parser
11888      */
11889     if (cm != MoveNumberOne)
11890         LoadGameOneMove(cm);
11891
11892     /* load the remaining moves from the file */
11893     while (LoadGameOneMove(EndOfFile)) {
11894       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11895       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11896     }
11897
11898     /* rewind to the start of the game */
11899     currentMove = backwardMostMove;
11900
11901     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11902
11903     if (oldGameMode == AnalyzeFile ||
11904         oldGameMode == AnalyzeMode) {
11905       AnalyzeFileEvent();
11906     }
11907
11908     if (!matchMode && pos >= 0) {
11909         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11910     } else
11911     if (matchMode || appData.timeDelay == 0) {
11912       ToEndEvent();
11913     } else if (appData.timeDelay > 0) {
11914       AutoPlayGameLoop();
11915     }
11916
11917     if (appData.debugMode)
11918         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11919
11920     loadFlag = 0; /* [HGM] true game starts */
11921     return TRUE;
11922 }
11923
11924 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11925 int
11926 ReloadPosition (int offset)
11927 {
11928     int positionNumber = lastLoadPositionNumber + offset;
11929     if (lastLoadPositionFP == NULL) {
11930         DisplayError(_("No position has been loaded yet"), 0);
11931         return FALSE;
11932     }
11933     if (positionNumber <= 0) {
11934         DisplayError(_("Can't back up any further"), 0);
11935         return FALSE;
11936     }
11937     return LoadPosition(lastLoadPositionFP, positionNumber,
11938                         lastLoadPositionTitle);
11939 }
11940
11941 /* Load the nth position from the given file */
11942 int
11943 LoadPositionFromFile (char *filename, int n, char *title)
11944 {
11945     FILE *f;
11946     char buf[MSG_SIZ];
11947
11948     if (strcmp(filename, "-") == 0) {
11949         return LoadPosition(stdin, n, "stdin");
11950     } else {
11951         f = fopen(filename, "rb");
11952         if (f == NULL) {
11953             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11954             DisplayError(buf, errno);
11955             return FALSE;
11956         } else {
11957             return LoadPosition(f, n, title);
11958         }
11959     }
11960 }
11961
11962 /* Load the nth position from the given open file, and close it */
11963 int
11964 LoadPosition (FILE *f, int positionNumber, char *title)
11965 {
11966     char *p, line[MSG_SIZ];
11967     Board initial_position;
11968     int i, j, fenMode, pn;
11969
11970     if (gameMode == Training )
11971         SetTrainingModeOff();
11972
11973     if (gameMode != BeginningOfGame) {
11974         Reset(FALSE, TRUE);
11975     }
11976     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11977         fclose(lastLoadPositionFP);
11978     }
11979     if (positionNumber == 0) positionNumber = 1;
11980     lastLoadPositionFP = f;
11981     lastLoadPositionNumber = positionNumber;
11982     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11983     if (first.pr == NoProc && !appData.noChessProgram) {
11984       StartChessProgram(&first);
11985       InitChessProgram(&first, FALSE);
11986     }
11987     pn = positionNumber;
11988     if (positionNumber < 0) {
11989         /* Negative position number means to seek to that byte offset */
11990         if (fseek(f, -positionNumber, 0) == -1) {
11991             DisplayError(_("Can't seek on position file"), 0);
11992             return FALSE;
11993         };
11994         pn = 1;
11995     } else {
11996         if (fseek(f, 0, 0) == -1) {
11997             if (f == lastLoadPositionFP ?
11998                 positionNumber == lastLoadPositionNumber + 1 :
11999                 positionNumber == 1) {
12000                 pn = 1;
12001             } else {
12002                 DisplayError(_("Can't seek on position file"), 0);
12003                 return FALSE;
12004             }
12005         }
12006     }
12007     /* See if this file is FEN or old-style xboard */
12008     if (fgets(line, MSG_SIZ, f) == NULL) {
12009         DisplayError(_("Position not found in file"), 0);
12010         return FALSE;
12011     }
12012     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12013     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12014
12015     if (pn >= 2) {
12016         if (fenMode || line[0] == '#') pn--;
12017         while (pn > 0) {
12018             /* skip positions before number pn */
12019             if (fgets(line, MSG_SIZ, f) == NULL) {
12020                 Reset(TRUE, TRUE);
12021                 DisplayError(_("Position not found in file"), 0);
12022                 return FALSE;
12023             }
12024             if (fenMode || line[0] == '#') pn--;
12025         }
12026     }
12027
12028     if (fenMode) {
12029         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12030             DisplayError(_("Bad FEN position in file"), 0);
12031             return FALSE;
12032         }
12033     } else {
12034         (void) fgets(line, MSG_SIZ, f);
12035         (void) fgets(line, MSG_SIZ, f);
12036
12037         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12038             (void) fgets(line, MSG_SIZ, f);
12039             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12040                 if (*p == ' ')
12041                   continue;
12042                 initial_position[i][j++] = CharToPiece(*p);
12043             }
12044         }
12045
12046         blackPlaysFirst = FALSE;
12047         if (!feof(f)) {
12048             (void) fgets(line, MSG_SIZ, f);
12049             if (strncmp(line, "black", strlen("black"))==0)
12050               blackPlaysFirst = TRUE;
12051         }
12052     }
12053     startedFromSetupPosition = TRUE;
12054
12055     CopyBoard(boards[0], initial_position);
12056     if (blackPlaysFirst) {
12057         currentMove = forwardMostMove = backwardMostMove = 1;
12058         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12059         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12060         CopyBoard(boards[1], initial_position);
12061         DisplayMessage("", _("Black to play"));
12062     } else {
12063         currentMove = forwardMostMove = backwardMostMove = 0;
12064         DisplayMessage("", _("White to play"));
12065     }
12066     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12067     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12068         SendToProgram("force\n", &first);
12069         SendBoard(&first, forwardMostMove);
12070     }
12071     if (appData.debugMode) {
12072 int i, j;
12073   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12074   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12075         fprintf(debugFP, "Load Position\n");
12076     }
12077
12078     if (positionNumber > 1) {
12079       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12080         DisplayTitle(line);
12081     } else {
12082         DisplayTitle(title);
12083     }
12084     gameMode = EditGame;
12085     ModeHighlight();
12086     ResetClocks();
12087     timeRemaining[0][1] = whiteTimeRemaining;
12088     timeRemaining[1][1] = blackTimeRemaining;
12089     DrawPosition(FALSE, boards[currentMove]);
12090
12091     return TRUE;
12092 }
12093
12094
12095 void
12096 CopyPlayerNameIntoFileName (char **dest, char *src)
12097 {
12098     while (*src != NULLCHAR && *src != ',') {
12099         if (*src == ' ') {
12100             *(*dest)++ = '_';
12101             src++;
12102         } else {
12103             *(*dest)++ = *src++;
12104         }
12105     }
12106 }
12107
12108 char *
12109 DefaultFileName (char *ext)
12110 {
12111     static char def[MSG_SIZ];
12112     char *p;
12113
12114     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12115         p = def;
12116         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12117         *p++ = '-';
12118         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12119         *p++ = '.';
12120         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12121     } else {
12122         def[0] = NULLCHAR;
12123     }
12124     return def;
12125 }
12126
12127 /* Save the current game to the given file */
12128 int
12129 SaveGameToFile (char *filename, int append)
12130 {
12131     FILE *f;
12132     char buf[MSG_SIZ];
12133     int result, i, t,tot=0;
12134
12135     if (strcmp(filename, "-") == 0) {
12136         return SaveGame(stdout, 0, NULL);
12137     } else {
12138         for(i=0; i<10; i++) { // upto 10 tries
12139              f = fopen(filename, append ? "a" : "w");
12140              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12141              if(f || errno != 13) break;
12142              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12143              tot += t;
12144         }
12145         if (f == NULL) {
12146             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12147             DisplayError(buf, errno);
12148             return FALSE;
12149         } else {
12150             safeStrCpy(buf, lastMsg, MSG_SIZ);
12151             DisplayMessage(_("Waiting for access to save file"), "");
12152             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12153             DisplayMessage(_("Saving game"), "");
12154             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12155             result = SaveGame(f, 0, NULL);
12156             DisplayMessage(buf, "");
12157             return result;
12158         }
12159     }
12160 }
12161
12162 char *
12163 SavePart (char *str)
12164 {
12165     static char buf[MSG_SIZ];
12166     char *p;
12167
12168     p = strchr(str, ' ');
12169     if (p == NULL) return str;
12170     strncpy(buf, str, p - str);
12171     buf[p - str] = NULLCHAR;
12172     return buf;
12173 }
12174
12175 #define PGN_MAX_LINE 75
12176
12177 #define PGN_SIDE_WHITE  0
12178 #define PGN_SIDE_BLACK  1
12179
12180 static int
12181 FindFirstMoveOutOfBook (int side)
12182 {
12183     int result = -1;
12184
12185     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12186         int index = backwardMostMove;
12187         int has_book_hit = 0;
12188
12189         if( (index % 2) != side ) {
12190             index++;
12191         }
12192
12193         while( index < forwardMostMove ) {
12194             /* Check to see if engine is in book */
12195             int depth = pvInfoList[index].depth;
12196             int score = pvInfoList[index].score;
12197             int in_book = 0;
12198
12199             if( depth <= 2 ) {
12200                 in_book = 1;
12201             }
12202             else if( score == 0 && depth == 63 ) {
12203                 in_book = 1; /* Zappa */
12204             }
12205             else if( score == 2 && depth == 99 ) {
12206                 in_book = 1; /* Abrok */
12207             }
12208
12209             has_book_hit += in_book;
12210
12211             if( ! in_book ) {
12212                 result = index;
12213
12214                 break;
12215             }
12216
12217             index += 2;
12218         }
12219     }
12220
12221     return result;
12222 }
12223
12224 void
12225 GetOutOfBookInfo (char * buf)
12226 {
12227     int oob[2];
12228     int i;
12229     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12230
12231     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12232     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12233
12234     *buf = '\0';
12235
12236     if( oob[0] >= 0 || oob[1] >= 0 ) {
12237         for( i=0; i<2; i++ ) {
12238             int idx = oob[i];
12239
12240             if( idx >= 0 ) {
12241                 if( i > 0 && oob[0] >= 0 ) {
12242                     strcat( buf, "   " );
12243                 }
12244
12245                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12246                 sprintf( buf+strlen(buf), "%s%.2f",
12247                     pvInfoList[idx].score >= 0 ? "+" : "",
12248                     pvInfoList[idx].score / 100.0 );
12249             }
12250         }
12251     }
12252 }
12253
12254 /* Save game in PGN style and close the file */
12255 int
12256 SaveGamePGN (FILE *f)
12257 {
12258     int i, offset, linelen, newblock;
12259     time_t tm;
12260 //    char *movetext;
12261     char numtext[32];
12262     int movelen, numlen, blank;
12263     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12264
12265     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12266
12267     tm = time((time_t *) NULL);
12268
12269     PrintPGNTags(f, &gameInfo);
12270
12271     if (backwardMostMove > 0 || startedFromSetupPosition) {
12272         char *fen = PositionToFEN(backwardMostMove, NULL);
12273         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12274         fprintf(f, "\n{--------------\n");
12275         PrintPosition(f, backwardMostMove);
12276         fprintf(f, "--------------}\n");
12277         free(fen);
12278     }
12279     else {
12280         /* [AS] Out of book annotation */
12281         if( appData.saveOutOfBookInfo ) {
12282             char buf[64];
12283
12284             GetOutOfBookInfo( buf );
12285
12286             if( buf[0] != '\0' ) {
12287                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12288             }
12289         }
12290
12291         fprintf(f, "\n");
12292     }
12293
12294     i = backwardMostMove;
12295     linelen = 0;
12296     newblock = TRUE;
12297
12298     while (i < forwardMostMove) {
12299         /* Print comments preceding this move */
12300         if (commentList[i] != NULL) {
12301             if (linelen > 0) fprintf(f, "\n");
12302             fprintf(f, "%s", commentList[i]);
12303             linelen = 0;
12304             newblock = TRUE;
12305         }
12306
12307         /* Format move number */
12308         if ((i % 2) == 0)
12309           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12310         else
12311           if (newblock)
12312             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12313           else
12314             numtext[0] = NULLCHAR;
12315
12316         numlen = strlen(numtext);
12317         newblock = FALSE;
12318
12319         /* Print move number */
12320         blank = linelen > 0 && numlen > 0;
12321         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12322             fprintf(f, "\n");
12323             linelen = 0;
12324             blank = 0;
12325         }
12326         if (blank) {
12327             fprintf(f, " ");
12328             linelen++;
12329         }
12330         fprintf(f, "%s", numtext);
12331         linelen += numlen;
12332
12333         /* Get move */
12334         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12335         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12336
12337         /* Print move */
12338         blank = linelen > 0 && movelen > 0;
12339         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12340             fprintf(f, "\n");
12341             linelen = 0;
12342             blank = 0;
12343         }
12344         if (blank) {
12345             fprintf(f, " ");
12346             linelen++;
12347         }
12348         fprintf(f, "%s", move_buffer);
12349         linelen += movelen;
12350
12351         /* [AS] Add PV info if present */
12352         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12353             /* [HGM] add time */
12354             char buf[MSG_SIZ]; int seconds;
12355
12356             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12357
12358             if( seconds <= 0)
12359               buf[0] = 0;
12360             else
12361               if( seconds < 30 )
12362                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12363               else
12364                 {
12365                   seconds = (seconds + 4)/10; // round to full seconds
12366                   if( seconds < 60 )
12367                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12368                   else
12369                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12370                 }
12371
12372             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12373                       pvInfoList[i].score >= 0 ? "+" : "",
12374                       pvInfoList[i].score / 100.0,
12375                       pvInfoList[i].depth,
12376                       buf );
12377
12378             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12379
12380             /* Print score/depth */
12381             blank = linelen > 0 && movelen > 0;
12382             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12383                 fprintf(f, "\n");
12384                 linelen = 0;
12385                 blank = 0;
12386             }
12387             if (blank) {
12388                 fprintf(f, " ");
12389                 linelen++;
12390             }
12391             fprintf(f, "%s", move_buffer);
12392             linelen += movelen;
12393         }
12394
12395         i++;
12396     }
12397
12398     /* Start a new line */
12399     if (linelen > 0) fprintf(f, "\n");
12400
12401     /* Print comments after last move */
12402     if (commentList[i] != NULL) {
12403         fprintf(f, "%s\n", commentList[i]);
12404     }
12405
12406     /* Print result */
12407     if (gameInfo.resultDetails != NULL &&
12408         gameInfo.resultDetails[0] != NULLCHAR) {
12409         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12410                 PGNResult(gameInfo.result));
12411     } else {
12412         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12413     }
12414
12415     fclose(f);
12416     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12417     return TRUE;
12418 }
12419
12420 /* Save game in old style and close the file */
12421 int
12422 SaveGameOldStyle (FILE *f)
12423 {
12424     int i, offset;
12425     time_t tm;
12426
12427     tm = time((time_t *) NULL);
12428
12429     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12430     PrintOpponents(f);
12431
12432     if (backwardMostMove > 0 || startedFromSetupPosition) {
12433         fprintf(f, "\n[--------------\n");
12434         PrintPosition(f, backwardMostMove);
12435         fprintf(f, "--------------]\n");
12436     } else {
12437         fprintf(f, "\n");
12438     }
12439
12440     i = backwardMostMove;
12441     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12442
12443     while (i < forwardMostMove) {
12444         if (commentList[i] != NULL) {
12445             fprintf(f, "[%s]\n", commentList[i]);
12446         }
12447
12448         if ((i % 2) == 1) {
12449             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12450             i++;
12451         } else {
12452             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12453             i++;
12454             if (commentList[i] != NULL) {
12455                 fprintf(f, "\n");
12456                 continue;
12457             }
12458             if (i >= forwardMostMove) {
12459                 fprintf(f, "\n");
12460                 break;
12461             }
12462             fprintf(f, "%s\n", parseList[i]);
12463             i++;
12464         }
12465     }
12466
12467     if (commentList[i] != NULL) {
12468         fprintf(f, "[%s]\n", commentList[i]);
12469     }
12470
12471     /* This isn't really the old style, but it's close enough */
12472     if (gameInfo.resultDetails != NULL &&
12473         gameInfo.resultDetails[0] != NULLCHAR) {
12474         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12475                 gameInfo.resultDetails);
12476     } else {
12477         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12478     }
12479
12480     fclose(f);
12481     return TRUE;
12482 }
12483
12484 /* Save the current game to open file f and close the file */
12485 int
12486 SaveGame (FILE *f, int dummy, char *dummy2)
12487 {
12488     if (gameMode == EditPosition) EditPositionDone(TRUE);
12489     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12490     if (appData.oldSaveStyle)
12491       return SaveGameOldStyle(f);
12492     else
12493       return SaveGamePGN(f);
12494 }
12495
12496 /* Save the current position to the given file */
12497 int
12498 SavePositionToFile (char *filename)
12499 {
12500     FILE *f;
12501     char buf[MSG_SIZ];
12502
12503     if (strcmp(filename, "-") == 0) {
12504         return SavePosition(stdout, 0, NULL);
12505     } else {
12506         f = fopen(filename, "a");
12507         if (f == NULL) {
12508             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12509             DisplayError(buf, errno);
12510             return FALSE;
12511         } else {
12512             safeStrCpy(buf, lastMsg, MSG_SIZ);
12513             DisplayMessage(_("Waiting for access to save file"), "");
12514             flock(fileno(f), LOCK_EX); // [HGM] lock
12515             DisplayMessage(_("Saving position"), "");
12516             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12517             SavePosition(f, 0, NULL);
12518             DisplayMessage(buf, "");
12519             return TRUE;
12520         }
12521     }
12522 }
12523
12524 /* Save the current position to the given open file and close the file */
12525 int
12526 SavePosition (FILE *f, int dummy, char *dummy2)
12527 {
12528     time_t tm;
12529     char *fen;
12530
12531     if (gameMode == EditPosition) EditPositionDone(TRUE);
12532     if (appData.oldSaveStyle) {
12533         tm = time((time_t *) NULL);
12534
12535         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12536         PrintOpponents(f);
12537         fprintf(f, "[--------------\n");
12538         PrintPosition(f, currentMove);
12539         fprintf(f, "--------------]\n");
12540     } else {
12541         fen = PositionToFEN(currentMove, NULL);
12542         fprintf(f, "%s\n", fen);
12543         free(fen);
12544     }
12545     fclose(f);
12546     return TRUE;
12547 }
12548
12549 void
12550 ReloadCmailMsgEvent (int unregister)
12551 {
12552 #if !WIN32
12553     static char *inFilename = NULL;
12554     static char *outFilename;
12555     int i;
12556     struct stat inbuf, outbuf;
12557     int status;
12558
12559     /* Any registered moves are unregistered if unregister is set, */
12560     /* i.e. invoked by the signal handler */
12561     if (unregister) {
12562         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12563             cmailMoveRegistered[i] = FALSE;
12564             if (cmailCommentList[i] != NULL) {
12565                 free(cmailCommentList[i]);
12566                 cmailCommentList[i] = NULL;
12567             }
12568         }
12569         nCmailMovesRegistered = 0;
12570     }
12571
12572     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12573         cmailResult[i] = CMAIL_NOT_RESULT;
12574     }
12575     nCmailResults = 0;
12576
12577     if (inFilename == NULL) {
12578         /* Because the filenames are static they only get malloced once  */
12579         /* and they never get freed                                      */
12580         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12581         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12582
12583         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12584         sprintf(outFilename, "%s.out", appData.cmailGameName);
12585     }
12586
12587     status = stat(outFilename, &outbuf);
12588     if (status < 0) {
12589         cmailMailedMove = FALSE;
12590     } else {
12591         status = stat(inFilename, &inbuf);
12592         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12593     }
12594
12595     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12596        counts the games, notes how each one terminated, etc.
12597
12598        It would be nice to remove this kludge and instead gather all
12599        the information while building the game list.  (And to keep it
12600        in the game list nodes instead of having a bunch of fixed-size
12601        parallel arrays.)  Note this will require getting each game's
12602        termination from the PGN tags, as the game list builder does
12603        not process the game moves.  --mann
12604        */
12605     cmailMsgLoaded = TRUE;
12606     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12607
12608     /* Load first game in the file or popup game menu */
12609     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12610
12611 #endif /* !WIN32 */
12612     return;
12613 }
12614
12615 int
12616 RegisterMove ()
12617 {
12618     FILE *f;
12619     char string[MSG_SIZ];
12620
12621     if (   cmailMailedMove
12622         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12623         return TRUE;            /* Allow free viewing  */
12624     }
12625
12626     /* Unregister move to ensure that we don't leave RegisterMove        */
12627     /* with the move registered when the conditions for registering no   */
12628     /* longer hold                                                       */
12629     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12630         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12631         nCmailMovesRegistered --;
12632
12633         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12634           {
12635               free(cmailCommentList[lastLoadGameNumber - 1]);
12636               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12637           }
12638     }
12639
12640     if (cmailOldMove == -1) {
12641         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12642         return FALSE;
12643     }
12644
12645     if (currentMove > cmailOldMove + 1) {
12646         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12647         return FALSE;
12648     }
12649
12650     if (currentMove < cmailOldMove) {
12651         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12652         return FALSE;
12653     }
12654
12655     if (forwardMostMove > currentMove) {
12656         /* Silently truncate extra moves */
12657         TruncateGame();
12658     }
12659
12660     if (   (currentMove == cmailOldMove + 1)
12661         || (   (currentMove == cmailOldMove)
12662             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12663                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12664         if (gameInfo.result != GameUnfinished) {
12665             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12666         }
12667
12668         if (commentList[currentMove] != NULL) {
12669             cmailCommentList[lastLoadGameNumber - 1]
12670               = StrSave(commentList[currentMove]);
12671         }
12672         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12673
12674         if (appData.debugMode)
12675           fprintf(debugFP, "Saving %s for game %d\n",
12676                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12677
12678         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12679
12680         f = fopen(string, "w");
12681         if (appData.oldSaveStyle) {
12682             SaveGameOldStyle(f); /* also closes the file */
12683
12684             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12685             f = fopen(string, "w");
12686             SavePosition(f, 0, NULL); /* also closes the file */
12687         } else {
12688             fprintf(f, "{--------------\n");
12689             PrintPosition(f, currentMove);
12690             fprintf(f, "--------------}\n\n");
12691
12692             SaveGame(f, 0, NULL); /* also closes the file*/
12693         }
12694
12695         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12696         nCmailMovesRegistered ++;
12697     } else if (nCmailGames == 1) {
12698         DisplayError(_("You have not made a move yet"), 0);
12699         return FALSE;
12700     }
12701
12702     return TRUE;
12703 }
12704
12705 void
12706 MailMoveEvent ()
12707 {
12708 #if !WIN32
12709     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12710     FILE *commandOutput;
12711     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12712     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12713     int nBuffers;
12714     int i;
12715     int archived;
12716     char *arcDir;
12717
12718     if (! cmailMsgLoaded) {
12719         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12720         return;
12721     }
12722
12723     if (nCmailGames == nCmailResults) {
12724         DisplayError(_("No unfinished games"), 0);
12725         return;
12726     }
12727
12728 #if CMAIL_PROHIBIT_REMAIL
12729     if (cmailMailedMove) {
12730       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);
12731         DisplayError(msg, 0);
12732         return;
12733     }
12734 #endif
12735
12736     if (! (cmailMailedMove || RegisterMove())) return;
12737
12738     if (   cmailMailedMove
12739         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12740       snprintf(string, MSG_SIZ, partCommandString,
12741                appData.debugMode ? " -v" : "", appData.cmailGameName);
12742         commandOutput = popen(string, "r");
12743
12744         if (commandOutput == NULL) {
12745             DisplayError(_("Failed to invoke cmail"), 0);
12746         } else {
12747             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12748                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12749             }
12750             if (nBuffers > 1) {
12751                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12752                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12753                 nBytes = MSG_SIZ - 1;
12754             } else {
12755                 (void) memcpy(msg, buffer, nBytes);
12756             }
12757             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12758
12759             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12760                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12761
12762                 archived = TRUE;
12763                 for (i = 0; i < nCmailGames; i ++) {
12764                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12765                         archived = FALSE;
12766                     }
12767                 }
12768                 if (   archived
12769                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12770                         != NULL)) {
12771                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12772                            arcDir,
12773                            appData.cmailGameName,
12774                            gameInfo.date);
12775                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12776                     cmailMsgLoaded = FALSE;
12777                 }
12778             }
12779
12780             DisplayInformation(msg);
12781             pclose(commandOutput);
12782         }
12783     } else {
12784         if ((*cmailMsg) != '\0') {
12785             DisplayInformation(cmailMsg);
12786         }
12787     }
12788
12789     return;
12790 #endif /* !WIN32 */
12791 }
12792
12793 char *
12794 CmailMsg ()
12795 {
12796 #if WIN32
12797     return NULL;
12798 #else
12799     int  prependComma = 0;
12800     char number[5];
12801     char string[MSG_SIZ];       /* Space for game-list */
12802     int  i;
12803
12804     if (!cmailMsgLoaded) return "";
12805
12806     if (cmailMailedMove) {
12807       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12808     } else {
12809         /* Create a list of games left */
12810       snprintf(string, MSG_SIZ, "[");
12811         for (i = 0; i < nCmailGames; i ++) {
12812             if (! (   cmailMoveRegistered[i]
12813                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12814                 if (prependComma) {
12815                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12816                 } else {
12817                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12818                     prependComma = 1;
12819                 }
12820
12821                 strcat(string, number);
12822             }
12823         }
12824         strcat(string, "]");
12825
12826         if (nCmailMovesRegistered + nCmailResults == 0) {
12827             switch (nCmailGames) {
12828               case 1:
12829                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12830                 break;
12831
12832               case 2:
12833                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12834                 break;
12835
12836               default:
12837                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12838                          nCmailGames);
12839                 break;
12840             }
12841         } else {
12842             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12843               case 1:
12844                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12845                          string);
12846                 break;
12847
12848               case 0:
12849                 if (nCmailResults == nCmailGames) {
12850                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12851                 } else {
12852                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12853                 }
12854                 break;
12855
12856               default:
12857                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12858                          string);
12859             }
12860         }
12861     }
12862     return cmailMsg;
12863 #endif /* WIN32 */
12864 }
12865
12866 void
12867 ResetGameEvent ()
12868 {
12869     if (gameMode == Training)
12870       SetTrainingModeOff();
12871
12872     Reset(TRUE, TRUE);
12873     cmailMsgLoaded = FALSE;
12874     if (appData.icsActive) {
12875       SendToICS(ics_prefix);
12876       SendToICS("refresh\n");
12877     }
12878 }
12879
12880 void
12881 ExitEvent (int status)
12882 {
12883     exiting++;
12884     if (exiting > 2) {
12885       /* Give up on clean exit */
12886       exit(status);
12887     }
12888     if (exiting > 1) {
12889       /* Keep trying for clean exit */
12890       return;
12891     }
12892
12893     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12894
12895     if (telnetISR != NULL) {
12896       RemoveInputSource(telnetISR);
12897     }
12898     if (icsPR != NoProc) {
12899       DestroyChildProcess(icsPR, TRUE);
12900     }
12901
12902     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12903     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12904
12905     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12906     /* make sure this other one finishes before killing it!                  */
12907     if(endingGame) { int count = 0;
12908         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12909         while(endingGame && count++ < 10) DoSleep(1);
12910         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12911     }
12912
12913     /* Kill off chess programs */
12914     if (first.pr != NoProc) {
12915         ExitAnalyzeMode();
12916
12917         DoSleep( appData.delayBeforeQuit );
12918         SendToProgram("quit\n", &first);
12919         DoSleep( appData.delayAfterQuit );
12920         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12921     }
12922     if (second.pr != NoProc) {
12923         DoSleep( appData.delayBeforeQuit );
12924         SendToProgram("quit\n", &second);
12925         DoSleep( appData.delayAfterQuit );
12926         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12927     }
12928     if (first.isr != NULL) {
12929         RemoveInputSource(first.isr);
12930     }
12931     if (second.isr != NULL) {
12932         RemoveInputSource(second.isr);
12933     }
12934
12935     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12936     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12937
12938     ShutDownFrontEnd();
12939     exit(status);
12940 }
12941
12942 void
12943 PauseEvent ()
12944 {
12945     if (appData.debugMode)
12946         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12947     if (pausing) {
12948         pausing = FALSE;
12949         ModeHighlight();
12950         if (gameMode == MachinePlaysWhite ||
12951             gameMode == MachinePlaysBlack) {
12952             StartClocks();
12953         } else {
12954             DisplayBothClocks();
12955         }
12956         if (gameMode == PlayFromGameFile) {
12957             if (appData.timeDelay >= 0)
12958                 AutoPlayGameLoop();
12959         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12960             Reset(FALSE, TRUE);
12961             SendToICS(ics_prefix);
12962             SendToICS("refresh\n");
12963         } else if (currentMove < forwardMostMove) {
12964             ForwardInner(forwardMostMove);
12965         }
12966         pauseExamInvalid = FALSE;
12967     } else {
12968         switch (gameMode) {
12969           default:
12970             return;
12971           case IcsExamining:
12972             pauseExamForwardMostMove = forwardMostMove;
12973             pauseExamInvalid = FALSE;
12974             /* fall through */
12975           case IcsObserving:
12976           case IcsPlayingWhite:
12977           case IcsPlayingBlack:
12978             pausing = TRUE;
12979             ModeHighlight();
12980             return;
12981           case PlayFromGameFile:
12982             (void) StopLoadGameTimer();
12983             pausing = TRUE;
12984             ModeHighlight();
12985             break;
12986           case BeginningOfGame:
12987             if (appData.icsActive) return;
12988             /* else fall through */
12989           case MachinePlaysWhite:
12990           case MachinePlaysBlack:
12991           case TwoMachinesPlay:
12992             if (forwardMostMove == 0)
12993               return;           /* don't pause if no one has moved */
12994             if ((gameMode == MachinePlaysWhite &&
12995                  !WhiteOnMove(forwardMostMove)) ||
12996                 (gameMode == MachinePlaysBlack &&
12997                  WhiteOnMove(forwardMostMove))) {
12998                 StopClocks();
12999             }
13000             pausing = TRUE;
13001             ModeHighlight();
13002             break;
13003         }
13004     }
13005 }
13006
13007 void
13008 EditCommentEvent ()
13009 {
13010     char title[MSG_SIZ];
13011
13012     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13013       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13014     } else {
13015       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13016                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13017                parseList[currentMove - 1]);
13018     }
13019
13020     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13021 }
13022
13023
13024 void
13025 EditTagsEvent ()
13026 {
13027     char *tags = PGNTags(&gameInfo);
13028     bookUp = FALSE;
13029     EditTagsPopUp(tags, NULL);
13030     free(tags);
13031 }
13032
13033 void
13034 AnalyzeModeEvent ()
13035 {
13036     if (appData.noChessProgram || gameMode == AnalyzeMode)
13037       return;
13038
13039     if (gameMode != AnalyzeFile) {
13040         if (!appData.icsEngineAnalyze) {
13041                EditGameEvent();
13042                if (gameMode != EditGame) return;
13043         }
13044         ResurrectChessProgram();
13045         SendToProgram("analyze\n", &first);
13046         first.analyzing = TRUE;
13047         /*first.maybeThinking = TRUE;*/
13048         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13049         EngineOutputPopUp();
13050     }
13051     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13052     pausing = FALSE;
13053     ModeHighlight();
13054     SetGameInfo();
13055
13056     StartAnalysisClock();
13057     GetTimeMark(&lastNodeCountTime);
13058     lastNodeCount = 0;
13059 }
13060
13061 void
13062 AnalyzeFileEvent ()
13063 {
13064     if (appData.noChessProgram || gameMode == AnalyzeFile)
13065       return;
13066
13067     if (gameMode != AnalyzeMode) {
13068         EditGameEvent();
13069         if (gameMode != EditGame) return;
13070         ResurrectChessProgram();
13071         SendToProgram("analyze\n", &first);
13072         first.analyzing = TRUE;
13073         /*first.maybeThinking = TRUE;*/
13074         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13075         EngineOutputPopUp();
13076     }
13077     gameMode = AnalyzeFile;
13078     pausing = FALSE;
13079     ModeHighlight();
13080     SetGameInfo();
13081
13082     StartAnalysisClock();
13083     GetTimeMark(&lastNodeCountTime);
13084     lastNodeCount = 0;
13085     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13086 }
13087
13088 void
13089 MachineWhiteEvent ()
13090 {
13091     char buf[MSG_SIZ];
13092     char *bookHit = NULL;
13093
13094     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13095       return;
13096
13097
13098     if (gameMode == PlayFromGameFile ||
13099         gameMode == TwoMachinesPlay  ||
13100         gameMode == Training         ||
13101         gameMode == AnalyzeMode      ||
13102         gameMode == EndOfGame)
13103         EditGameEvent();
13104
13105     if (gameMode == EditPosition)
13106         EditPositionDone(TRUE);
13107
13108     if (!WhiteOnMove(currentMove)) {
13109         DisplayError(_("It is not White's turn"), 0);
13110         return;
13111     }
13112
13113     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13114       ExitAnalyzeMode();
13115
13116     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13117         gameMode == AnalyzeFile)
13118         TruncateGame();
13119
13120     ResurrectChessProgram();    /* in case it isn't running */
13121     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13122         gameMode = MachinePlaysWhite;
13123         ResetClocks();
13124     } else
13125     gameMode = MachinePlaysWhite;
13126     pausing = FALSE;
13127     ModeHighlight();
13128     SetGameInfo();
13129     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13130     DisplayTitle(buf);
13131     if (first.sendName) {
13132       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13133       SendToProgram(buf, &first);
13134     }
13135     if (first.sendTime) {
13136       if (first.useColors) {
13137         SendToProgram("black\n", &first); /*gnu kludge*/
13138       }
13139       SendTimeRemaining(&first, TRUE);
13140     }
13141     if (first.useColors) {
13142       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13143     }
13144     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13145     SetMachineThinkingEnables();
13146     first.maybeThinking = TRUE;
13147     StartClocks();
13148     firstMove = FALSE;
13149
13150     if (appData.autoFlipView && !flipView) {
13151       flipView = !flipView;
13152       DrawPosition(FALSE, NULL);
13153       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13154     }
13155
13156     if(bookHit) { // [HGM] book: simulate book reply
13157         static char bookMove[MSG_SIZ]; // a bit generous?
13158
13159         programStats.nodes = programStats.depth = programStats.time =
13160         programStats.score = programStats.got_only_move = 0;
13161         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13162
13163         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13164         strcat(bookMove, bookHit);
13165         HandleMachineMove(bookMove, &first);
13166     }
13167 }
13168
13169 void
13170 MachineBlackEvent ()
13171 {
13172   char buf[MSG_SIZ];
13173   char *bookHit = NULL;
13174
13175     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13176         return;
13177
13178
13179     if (gameMode == PlayFromGameFile ||
13180         gameMode == TwoMachinesPlay  ||
13181         gameMode == Training         ||
13182         gameMode == AnalyzeMode      ||
13183         gameMode == EndOfGame)
13184         EditGameEvent();
13185
13186     if (gameMode == EditPosition)
13187         EditPositionDone(TRUE);
13188
13189     if (WhiteOnMove(currentMove)) {
13190         DisplayError(_("It is not Black's turn"), 0);
13191         return;
13192     }
13193
13194     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13195       ExitAnalyzeMode();
13196
13197     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13198         gameMode == AnalyzeFile)
13199         TruncateGame();
13200
13201     ResurrectChessProgram();    /* in case it isn't running */
13202     gameMode = MachinePlaysBlack;
13203     pausing = FALSE;
13204     ModeHighlight();
13205     SetGameInfo();
13206     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13207     DisplayTitle(buf);
13208     if (first.sendName) {
13209       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13210       SendToProgram(buf, &first);
13211     }
13212     if (first.sendTime) {
13213       if (first.useColors) {
13214         SendToProgram("white\n", &first); /*gnu kludge*/
13215       }
13216       SendTimeRemaining(&first, FALSE);
13217     }
13218     if (first.useColors) {
13219       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13220     }
13221     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13222     SetMachineThinkingEnables();
13223     first.maybeThinking = TRUE;
13224     StartClocks();
13225
13226     if (appData.autoFlipView && flipView) {
13227       flipView = !flipView;
13228       DrawPosition(FALSE, NULL);
13229       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13230     }
13231     if(bookHit) { // [HGM] book: simulate book reply
13232         static char bookMove[MSG_SIZ]; // a bit generous?
13233
13234         programStats.nodes = programStats.depth = programStats.time =
13235         programStats.score = programStats.got_only_move = 0;
13236         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13237
13238         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13239         strcat(bookMove, bookHit);
13240         HandleMachineMove(bookMove, &first);
13241     }
13242 }
13243
13244
13245 void
13246 DisplayTwoMachinesTitle ()
13247 {
13248     char buf[MSG_SIZ];
13249     if (appData.matchGames > 0) {
13250         if(appData.tourneyFile[0]) {
13251           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13252                    gameInfo.white, _("vs."), gameInfo.black,
13253                    nextGame+1, appData.matchGames+1,
13254                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13255         } else 
13256         if (first.twoMachinesColor[0] == 'w') {
13257           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13258                    gameInfo.white, _("vs."),  gameInfo.black,
13259                    first.matchWins, second.matchWins,
13260                    matchGame - 1 - (first.matchWins + second.matchWins));
13261         } else {
13262           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13263                    gameInfo.white, _("vs."), gameInfo.black,
13264                    second.matchWins, first.matchWins,
13265                    matchGame - 1 - (first.matchWins + second.matchWins));
13266         }
13267     } else {
13268       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13269     }
13270     DisplayTitle(buf);
13271 }
13272
13273 void
13274 SettingsMenuIfReady ()
13275 {
13276   if (second.lastPing != second.lastPong) {
13277     DisplayMessage("", _("Waiting for second chess program"));
13278     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13279     return;
13280   }
13281   ThawUI();
13282   DisplayMessage("", "");
13283   SettingsPopUp(&second);
13284 }
13285
13286 int
13287 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13288 {
13289     char buf[MSG_SIZ];
13290     if (cps->pr == NoProc) {
13291         StartChessProgram(cps);
13292         if (cps->protocolVersion == 1) {
13293           retry();
13294         } else {
13295           /* kludge: allow timeout for initial "feature" command */
13296           FreezeUI();
13297           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13298           DisplayMessage("", buf);
13299           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13300         }
13301         return 1;
13302     }
13303     return 0;
13304 }
13305
13306 void
13307 TwoMachinesEvent P((void))
13308 {
13309     int i;
13310     char buf[MSG_SIZ];
13311     ChessProgramState *onmove;
13312     char *bookHit = NULL;
13313     static int stalling = 0;
13314     TimeMark now;
13315     long wait;
13316
13317     if (appData.noChessProgram) return;
13318
13319     switch (gameMode) {
13320       case TwoMachinesPlay:
13321         return;
13322       case MachinePlaysWhite:
13323       case MachinePlaysBlack:
13324         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13325             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13326             return;
13327         }
13328         /* fall through */
13329       case BeginningOfGame:
13330       case PlayFromGameFile:
13331       case EndOfGame:
13332         EditGameEvent();
13333         if (gameMode != EditGame) return;
13334         break;
13335       case EditPosition:
13336         EditPositionDone(TRUE);
13337         break;
13338       case AnalyzeMode:
13339       case AnalyzeFile:
13340         ExitAnalyzeMode();
13341         break;
13342       case EditGame:
13343       default:
13344         break;
13345     }
13346
13347 //    forwardMostMove = currentMove;
13348     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13349
13350     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13351
13352     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13353     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13354       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13355       return;
13356     }
13357     if(!stalling) {
13358       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13359       SendToProgram("force\n", &second);
13360       stalling = 1;
13361       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13362       return;
13363     }
13364     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13365     if(appData.matchPause>10000 || appData.matchPause<10)
13366                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13367     wait = SubtractTimeMarks(&now, &pauseStart);
13368     if(wait < appData.matchPause) {
13369         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13370         return;
13371     }
13372     stalling = 0;
13373     DisplayMessage("", "");
13374     if (startedFromSetupPosition) {
13375         SendBoard(&second, backwardMostMove);
13376     if (appData.debugMode) {
13377         fprintf(debugFP, "Two Machines\n");
13378     }
13379     }
13380     for (i = backwardMostMove; i < forwardMostMove; i++) {
13381         SendMoveToProgram(i, &second);
13382     }
13383
13384     gameMode = TwoMachinesPlay;
13385     pausing = FALSE;
13386     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13387     SetGameInfo();
13388     DisplayTwoMachinesTitle();
13389     firstMove = TRUE;
13390     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13391         onmove = &first;
13392     } else {
13393         onmove = &second;
13394     }
13395     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13396     SendToProgram(first.computerString, &first);
13397     if (first.sendName) {
13398       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13399       SendToProgram(buf, &first);
13400     }
13401     SendToProgram(second.computerString, &second);
13402     if (second.sendName) {
13403       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13404       SendToProgram(buf, &second);
13405     }
13406
13407     ResetClocks();
13408     if (!first.sendTime || !second.sendTime) {
13409         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13410         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13411     }
13412     if (onmove->sendTime) {
13413       if (onmove->useColors) {
13414         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13415       }
13416       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13417     }
13418     if (onmove->useColors) {
13419       SendToProgram(onmove->twoMachinesColor, onmove);
13420     }
13421     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13422 //    SendToProgram("go\n", onmove);
13423     onmove->maybeThinking = TRUE;
13424     SetMachineThinkingEnables();
13425
13426     StartClocks();
13427
13428     if(bookHit) { // [HGM] book: simulate book reply
13429         static char bookMove[MSG_SIZ]; // a bit generous?
13430
13431         programStats.nodes = programStats.depth = programStats.time =
13432         programStats.score = programStats.got_only_move = 0;
13433         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13434
13435         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13436         strcat(bookMove, bookHit);
13437         savedMessage = bookMove; // args for deferred call
13438         savedState = onmove;
13439         ScheduleDelayedEvent(DeferredBookMove, 1);
13440     }
13441 }
13442
13443 void
13444 TrainingEvent ()
13445 {
13446     if (gameMode == Training) {
13447       SetTrainingModeOff();
13448       gameMode = PlayFromGameFile;
13449       DisplayMessage("", _("Training mode off"));
13450     } else {
13451       gameMode = Training;
13452       animateTraining = appData.animate;
13453
13454       /* make sure we are not already at the end of the game */
13455       if (currentMove < forwardMostMove) {
13456         SetTrainingModeOn();
13457         DisplayMessage("", _("Training mode on"));
13458       } else {
13459         gameMode = PlayFromGameFile;
13460         DisplayError(_("Already at end of game"), 0);
13461       }
13462     }
13463     ModeHighlight();
13464 }
13465
13466 void
13467 IcsClientEvent ()
13468 {
13469     if (!appData.icsActive) return;
13470     switch (gameMode) {
13471       case IcsPlayingWhite:
13472       case IcsPlayingBlack:
13473       case IcsObserving:
13474       case IcsIdle:
13475       case BeginningOfGame:
13476       case IcsExamining:
13477         return;
13478
13479       case EditGame:
13480         break;
13481
13482       case EditPosition:
13483         EditPositionDone(TRUE);
13484         break;
13485
13486       case AnalyzeMode:
13487       case AnalyzeFile:
13488         ExitAnalyzeMode();
13489         break;
13490
13491       default:
13492         EditGameEvent();
13493         break;
13494     }
13495
13496     gameMode = IcsIdle;
13497     ModeHighlight();
13498     return;
13499 }
13500
13501 void
13502 EditGameEvent ()
13503 {
13504     int i;
13505
13506     switch (gameMode) {
13507       case Training:
13508         SetTrainingModeOff();
13509         break;
13510       case MachinePlaysWhite:
13511       case MachinePlaysBlack:
13512       case BeginningOfGame:
13513         SendToProgram("force\n", &first);
13514         SetUserThinkingEnables();
13515         break;
13516       case PlayFromGameFile:
13517         (void) StopLoadGameTimer();
13518         if (gameFileFP != NULL) {
13519             gameFileFP = NULL;
13520         }
13521         break;
13522       case EditPosition:
13523         EditPositionDone(TRUE);
13524         break;
13525       case AnalyzeMode:
13526       case AnalyzeFile:
13527         ExitAnalyzeMode();
13528         SendToProgram("force\n", &first);
13529         break;
13530       case TwoMachinesPlay:
13531         GameEnds(EndOfFile, NULL, GE_PLAYER);
13532         ResurrectChessProgram();
13533         SetUserThinkingEnables();
13534         break;
13535       case EndOfGame:
13536         ResurrectChessProgram();
13537         break;
13538       case IcsPlayingBlack:
13539       case IcsPlayingWhite:
13540         DisplayError(_("Warning: You are still playing a game"), 0);
13541         break;
13542       case IcsObserving:
13543         DisplayError(_("Warning: You are still observing a game"), 0);
13544         break;
13545       case IcsExamining:
13546         DisplayError(_("Warning: You are still examining a game"), 0);
13547         break;
13548       case IcsIdle:
13549         break;
13550       case EditGame:
13551       default:
13552         return;
13553     }
13554
13555     pausing = FALSE;
13556     StopClocks();
13557     first.offeredDraw = second.offeredDraw = 0;
13558
13559     if (gameMode == PlayFromGameFile) {
13560         whiteTimeRemaining = timeRemaining[0][currentMove];
13561         blackTimeRemaining = timeRemaining[1][currentMove];
13562         DisplayTitle("");
13563     }
13564
13565     if (gameMode == MachinePlaysWhite ||
13566         gameMode == MachinePlaysBlack ||
13567         gameMode == TwoMachinesPlay ||
13568         gameMode == EndOfGame) {
13569         i = forwardMostMove;
13570         while (i > currentMove) {
13571             SendToProgram("undo\n", &first);
13572             i--;
13573         }
13574         if(!adjustedClock) {
13575         whiteTimeRemaining = timeRemaining[0][currentMove];
13576         blackTimeRemaining = timeRemaining[1][currentMove];
13577         DisplayBothClocks();
13578         }
13579         if (whiteFlag || blackFlag) {
13580             whiteFlag = blackFlag = 0;
13581         }
13582         DisplayTitle("");
13583     }
13584
13585     gameMode = EditGame;
13586     ModeHighlight();
13587     SetGameInfo();
13588 }
13589
13590
13591 void
13592 EditPositionEvent ()
13593 {
13594     if (gameMode == EditPosition) {
13595         EditGameEvent();
13596         return;
13597     }
13598
13599     EditGameEvent();
13600     if (gameMode != EditGame) return;
13601
13602     gameMode = EditPosition;
13603     ModeHighlight();
13604     SetGameInfo();
13605     if (currentMove > 0)
13606       CopyBoard(boards[0], boards[currentMove]);
13607
13608     blackPlaysFirst = !WhiteOnMove(currentMove);
13609     ResetClocks();
13610     currentMove = forwardMostMove = backwardMostMove = 0;
13611     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13612     DisplayMove(-1);
13613 }
13614
13615 void
13616 ExitAnalyzeMode ()
13617 {
13618     /* [DM] icsEngineAnalyze - possible call from other functions */
13619     if (appData.icsEngineAnalyze) {
13620         appData.icsEngineAnalyze = FALSE;
13621
13622         DisplayMessage("",_("Close ICS engine analyze..."));
13623     }
13624     if (first.analysisSupport && first.analyzing) {
13625       SendToProgram("exit\n", &first);
13626       first.analyzing = FALSE;
13627     }
13628     thinkOutput[0] = NULLCHAR;
13629 }
13630
13631 void
13632 EditPositionDone (Boolean fakeRights)
13633 {
13634     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13635
13636     startedFromSetupPosition = TRUE;
13637     InitChessProgram(&first, FALSE);
13638     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13639       boards[0][EP_STATUS] = EP_NONE;
13640       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13641     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13642         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13643         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13644       } else boards[0][CASTLING][2] = NoRights;
13645     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13646         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13647         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13648       } else boards[0][CASTLING][5] = NoRights;
13649     }
13650     SendToProgram("force\n", &first);
13651     if (blackPlaysFirst) {
13652         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13653         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13654         currentMove = forwardMostMove = backwardMostMove = 1;
13655         CopyBoard(boards[1], boards[0]);
13656     } else {
13657         currentMove = forwardMostMove = backwardMostMove = 0;
13658     }
13659     SendBoard(&first, forwardMostMove);
13660     if (appData.debugMode) {
13661         fprintf(debugFP, "EditPosDone\n");
13662     }
13663     DisplayTitle("");
13664     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13665     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13666     gameMode = EditGame;
13667     ModeHighlight();
13668     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13669     ClearHighlights(); /* [AS] */
13670 }
13671
13672 /* Pause for `ms' milliseconds */
13673 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13674 void
13675 TimeDelay (long ms)
13676 {
13677     TimeMark m1, m2;
13678
13679     GetTimeMark(&m1);
13680     do {
13681         GetTimeMark(&m2);
13682     } while (SubtractTimeMarks(&m2, &m1) < ms);
13683 }
13684
13685 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13686 void
13687 SendMultiLineToICS (char *buf)
13688 {
13689     char temp[MSG_SIZ+1], *p;
13690     int len;
13691
13692     len = strlen(buf);
13693     if (len > MSG_SIZ)
13694       len = MSG_SIZ;
13695
13696     strncpy(temp, buf, len);
13697     temp[len] = 0;
13698
13699     p = temp;
13700     while (*p) {
13701         if (*p == '\n' || *p == '\r')
13702           *p = ' ';
13703         ++p;
13704     }
13705
13706     strcat(temp, "\n");
13707     SendToICS(temp);
13708     SendToPlayer(temp, strlen(temp));
13709 }
13710
13711 void
13712 SetWhiteToPlayEvent ()
13713 {
13714     if (gameMode == EditPosition) {
13715         blackPlaysFirst = FALSE;
13716         DisplayBothClocks();    /* works because currentMove is 0 */
13717     } else if (gameMode == IcsExamining) {
13718         SendToICS(ics_prefix);
13719         SendToICS("tomove white\n");
13720     }
13721 }
13722
13723 void
13724 SetBlackToPlayEvent ()
13725 {
13726     if (gameMode == EditPosition) {
13727         blackPlaysFirst = TRUE;
13728         currentMove = 1;        /* kludge */
13729         DisplayBothClocks();
13730         currentMove = 0;
13731     } else if (gameMode == IcsExamining) {
13732         SendToICS(ics_prefix);
13733         SendToICS("tomove black\n");
13734     }
13735 }
13736
13737 void
13738 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13739 {
13740     char buf[MSG_SIZ];
13741     ChessSquare piece = boards[0][y][x];
13742
13743     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13744
13745     switch (selection) {
13746       case ClearBoard:
13747         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13748             SendToICS(ics_prefix);
13749             SendToICS("bsetup clear\n");
13750         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13751             SendToICS(ics_prefix);
13752             SendToICS("clearboard\n");
13753         } else {
13754             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13755                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13756                 for (y = 0; y < BOARD_HEIGHT; y++) {
13757                     if (gameMode == IcsExamining) {
13758                         if (boards[currentMove][y][x] != EmptySquare) {
13759                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13760                                     AAA + x, ONE + y);
13761                             SendToICS(buf);
13762                         }
13763                     } else {
13764                         boards[0][y][x] = p;
13765                     }
13766                 }
13767             }
13768         }
13769         if (gameMode == EditPosition) {
13770             DrawPosition(FALSE, boards[0]);
13771         }
13772         break;
13773
13774       case WhitePlay:
13775         SetWhiteToPlayEvent();
13776         break;
13777
13778       case BlackPlay:
13779         SetBlackToPlayEvent();
13780         break;
13781
13782       case EmptySquare:
13783         if (gameMode == IcsExamining) {
13784             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13785             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13786             SendToICS(buf);
13787         } else {
13788             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13789                 if(x == BOARD_LEFT-2) {
13790                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13791                     boards[0][y][1] = 0;
13792                 } else
13793                 if(x == BOARD_RGHT+1) {
13794                     if(y >= gameInfo.holdingsSize) break;
13795                     boards[0][y][BOARD_WIDTH-2] = 0;
13796                 } else break;
13797             }
13798             boards[0][y][x] = EmptySquare;
13799             DrawPosition(FALSE, boards[0]);
13800         }
13801         break;
13802
13803       case PromotePiece:
13804         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13805            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13806             selection = (ChessSquare) (PROMOTED piece);
13807         } else if(piece == EmptySquare) selection = WhiteSilver;
13808         else selection = (ChessSquare)((int)piece - 1);
13809         goto defaultlabel;
13810
13811       case DemotePiece:
13812         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13813            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13814             selection = (ChessSquare) (DEMOTED piece);
13815         } else if(piece == EmptySquare) selection = BlackSilver;
13816         else selection = (ChessSquare)((int)piece + 1);
13817         goto defaultlabel;
13818
13819       case WhiteQueen:
13820       case BlackQueen:
13821         if(gameInfo.variant == VariantShatranj ||
13822            gameInfo.variant == VariantXiangqi  ||
13823            gameInfo.variant == VariantCourier  ||
13824            gameInfo.variant == VariantMakruk     )
13825             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13826         goto defaultlabel;
13827
13828       case WhiteKing:
13829       case BlackKing:
13830         if(gameInfo.variant == VariantXiangqi)
13831             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13832         if(gameInfo.variant == VariantKnightmate)
13833             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13834       default:
13835         defaultlabel:
13836         if (gameMode == IcsExamining) {
13837             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13838             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13839                      PieceToChar(selection), AAA + x, ONE + y);
13840             SendToICS(buf);
13841         } else {
13842             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13843                 int n;
13844                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13845                     n = PieceToNumber(selection - BlackPawn);
13846                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13847                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13848                     boards[0][BOARD_HEIGHT-1-n][1]++;
13849                 } else
13850                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13851                     n = PieceToNumber(selection);
13852                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13853                     boards[0][n][BOARD_WIDTH-1] = selection;
13854                     boards[0][n][BOARD_WIDTH-2]++;
13855                 }
13856             } else
13857             boards[0][y][x] = selection;
13858             DrawPosition(TRUE, boards[0]);
13859         }
13860         break;
13861     }
13862 }
13863
13864
13865 void
13866 DropMenuEvent (ChessSquare selection, int x, int y)
13867 {
13868     ChessMove moveType;
13869
13870     switch (gameMode) {
13871       case IcsPlayingWhite:
13872       case MachinePlaysBlack:
13873         if (!WhiteOnMove(currentMove)) {
13874             DisplayMoveError(_("It is Black's turn"));
13875             return;
13876         }
13877         moveType = WhiteDrop;
13878         break;
13879       case IcsPlayingBlack:
13880       case MachinePlaysWhite:
13881         if (WhiteOnMove(currentMove)) {
13882             DisplayMoveError(_("It is White's turn"));
13883             return;
13884         }
13885         moveType = BlackDrop;
13886         break;
13887       case EditGame:
13888         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13889         break;
13890       default:
13891         return;
13892     }
13893
13894     if (moveType == BlackDrop && selection < BlackPawn) {
13895       selection = (ChessSquare) ((int) selection
13896                                  + (int) BlackPawn - (int) WhitePawn);
13897     }
13898     if (boards[currentMove][y][x] != EmptySquare) {
13899         DisplayMoveError(_("That square is occupied"));
13900         return;
13901     }
13902
13903     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13904 }
13905
13906 void
13907 AcceptEvent ()
13908 {
13909     /* Accept a pending offer of any kind from opponent */
13910
13911     if (appData.icsActive) {
13912         SendToICS(ics_prefix);
13913         SendToICS("accept\n");
13914     } else if (cmailMsgLoaded) {
13915         if (currentMove == cmailOldMove &&
13916             commentList[cmailOldMove] != NULL &&
13917             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13918                    "Black offers a draw" : "White offers a draw")) {
13919             TruncateGame();
13920             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13921             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13922         } else {
13923             DisplayError(_("There is no pending offer on this move"), 0);
13924             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13925         }
13926     } else {
13927         /* Not used for offers from chess program */
13928     }
13929 }
13930
13931 void
13932 DeclineEvent ()
13933 {
13934     /* Decline a pending offer of any kind from opponent */
13935
13936     if (appData.icsActive) {
13937         SendToICS(ics_prefix);
13938         SendToICS("decline\n");
13939     } else if (cmailMsgLoaded) {
13940         if (currentMove == cmailOldMove &&
13941             commentList[cmailOldMove] != NULL &&
13942             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13943                    "Black offers a draw" : "White offers a draw")) {
13944 #ifdef NOTDEF
13945             AppendComment(cmailOldMove, "Draw declined", TRUE);
13946             DisplayComment(cmailOldMove - 1, "Draw declined");
13947 #endif /*NOTDEF*/
13948         } else {
13949             DisplayError(_("There is no pending offer on this move"), 0);
13950         }
13951     } else {
13952         /* Not used for offers from chess program */
13953     }
13954 }
13955
13956 void
13957 RematchEvent ()
13958 {
13959     /* Issue ICS rematch command */
13960     if (appData.icsActive) {
13961         SendToICS(ics_prefix);
13962         SendToICS("rematch\n");
13963     }
13964 }
13965
13966 void
13967 CallFlagEvent ()
13968 {
13969     /* Call your opponent's flag (claim a win on time) */
13970     if (appData.icsActive) {
13971         SendToICS(ics_prefix);
13972         SendToICS("flag\n");
13973     } else {
13974         switch (gameMode) {
13975           default:
13976             return;
13977           case MachinePlaysWhite:
13978             if (whiteFlag) {
13979                 if (blackFlag)
13980                   GameEnds(GameIsDrawn, "Both players ran out of time",
13981                            GE_PLAYER);
13982                 else
13983                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13984             } else {
13985                 DisplayError(_("Your opponent is not out of time"), 0);
13986             }
13987             break;
13988           case MachinePlaysBlack:
13989             if (blackFlag) {
13990                 if (whiteFlag)
13991                   GameEnds(GameIsDrawn, "Both players ran out of time",
13992                            GE_PLAYER);
13993                 else
13994                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13995             } else {
13996                 DisplayError(_("Your opponent is not out of time"), 0);
13997             }
13998             break;
13999         }
14000     }
14001 }
14002
14003 void
14004 ClockClick (int which)
14005 {       // [HGM] code moved to back-end from winboard.c
14006         if(which) { // black clock
14007           if (gameMode == EditPosition || gameMode == IcsExamining) {
14008             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14009             SetBlackToPlayEvent();
14010           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14011           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14012           } else if (shiftKey) {
14013             AdjustClock(which, -1);
14014           } else if (gameMode == IcsPlayingWhite ||
14015                      gameMode == MachinePlaysBlack) {
14016             CallFlagEvent();
14017           }
14018         } else { // white clock
14019           if (gameMode == EditPosition || gameMode == IcsExamining) {
14020             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14021             SetWhiteToPlayEvent();
14022           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14023           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14024           } else if (shiftKey) {
14025             AdjustClock(which, -1);
14026           } else if (gameMode == IcsPlayingBlack ||
14027                    gameMode == MachinePlaysWhite) {
14028             CallFlagEvent();
14029           }
14030         }
14031 }
14032
14033 void
14034 DrawEvent ()
14035 {
14036     /* Offer draw or accept pending draw offer from opponent */
14037
14038     if (appData.icsActive) {
14039         /* Note: tournament rules require draw offers to be
14040            made after you make your move but before you punch
14041            your clock.  Currently ICS doesn't let you do that;
14042            instead, you immediately punch your clock after making
14043            a move, but you can offer a draw at any time. */
14044
14045         SendToICS(ics_prefix);
14046         SendToICS("draw\n");
14047         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14048     } else if (cmailMsgLoaded) {
14049         if (currentMove == cmailOldMove &&
14050             commentList[cmailOldMove] != NULL &&
14051             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14052                    "Black offers a draw" : "White offers a draw")) {
14053             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14054             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14055         } else if (currentMove == cmailOldMove + 1) {
14056             char *offer = WhiteOnMove(cmailOldMove) ?
14057               "White offers a draw" : "Black offers a draw";
14058             AppendComment(currentMove, offer, TRUE);
14059             DisplayComment(currentMove - 1, offer);
14060             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14061         } else {
14062             DisplayError(_("You must make your move before offering a draw"), 0);
14063             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14064         }
14065     } else if (first.offeredDraw) {
14066         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14067     } else {
14068         if (first.sendDrawOffers) {
14069             SendToProgram("draw\n", &first);
14070             userOfferedDraw = TRUE;
14071         }
14072     }
14073 }
14074
14075 void
14076 AdjournEvent ()
14077 {
14078     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14079
14080     if (appData.icsActive) {
14081         SendToICS(ics_prefix);
14082         SendToICS("adjourn\n");
14083     } else {
14084         /* Currently GNU Chess doesn't offer or accept Adjourns */
14085     }
14086 }
14087
14088
14089 void
14090 AbortEvent ()
14091 {
14092     /* Offer Abort or accept pending Abort offer from opponent */
14093
14094     if (appData.icsActive) {
14095         SendToICS(ics_prefix);
14096         SendToICS("abort\n");
14097     } else {
14098         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14099     }
14100 }
14101
14102 void
14103 ResignEvent ()
14104 {
14105     /* Resign.  You can do this even if it's not your turn. */
14106
14107     if (appData.icsActive) {
14108         SendToICS(ics_prefix);
14109         SendToICS("resign\n");
14110     } else {
14111         switch (gameMode) {
14112           case MachinePlaysWhite:
14113             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14114             break;
14115           case MachinePlaysBlack:
14116             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14117             break;
14118           case EditGame:
14119             if (cmailMsgLoaded) {
14120                 TruncateGame();
14121                 if (WhiteOnMove(cmailOldMove)) {
14122                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14123                 } else {
14124                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14125                 }
14126                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14127             }
14128             break;
14129           default:
14130             break;
14131         }
14132     }
14133 }
14134
14135
14136 void
14137 StopObservingEvent ()
14138 {
14139     /* Stop observing current games */
14140     SendToICS(ics_prefix);
14141     SendToICS("unobserve\n");
14142 }
14143
14144 void
14145 StopExaminingEvent ()
14146 {
14147     /* Stop observing current game */
14148     SendToICS(ics_prefix);
14149     SendToICS("unexamine\n");
14150 }
14151
14152 void
14153 ForwardInner (int target)
14154 {
14155     int limit;
14156
14157     if (appData.debugMode)
14158         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14159                 target, currentMove, forwardMostMove);
14160
14161     if (gameMode == EditPosition)
14162       return;
14163
14164     MarkTargetSquares(1);
14165
14166     if (gameMode == PlayFromGameFile && !pausing)
14167       PauseEvent();
14168
14169     if (gameMode == IcsExamining && pausing)
14170       limit = pauseExamForwardMostMove;
14171     else
14172       limit = forwardMostMove;
14173
14174     if (target > limit) target = limit;
14175
14176     if (target > 0 && moveList[target - 1][0]) {
14177         int fromX, fromY, toX, toY;
14178         toX = moveList[target - 1][2] - AAA;
14179         toY = moveList[target - 1][3] - ONE;
14180         if (moveList[target - 1][1] == '@') {
14181             if (appData.highlightLastMove) {
14182                 SetHighlights(-1, -1, toX, toY);
14183             }
14184         } else {
14185             fromX = moveList[target - 1][0] - AAA;
14186             fromY = moveList[target - 1][1] - ONE;
14187             if (target == currentMove + 1) {
14188                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14189             }
14190             if (appData.highlightLastMove) {
14191                 SetHighlights(fromX, fromY, toX, toY);
14192             }
14193         }
14194     }
14195     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14196         gameMode == Training || gameMode == PlayFromGameFile ||
14197         gameMode == AnalyzeFile) {
14198         while (currentMove < target) {
14199             SendMoveToProgram(currentMove++, &first);
14200         }
14201     } else {
14202         currentMove = target;
14203     }
14204
14205     if (gameMode == EditGame || gameMode == EndOfGame) {
14206         whiteTimeRemaining = timeRemaining[0][currentMove];
14207         blackTimeRemaining = timeRemaining[1][currentMove];
14208     }
14209     DisplayBothClocks();
14210     DisplayMove(currentMove - 1);
14211     DrawPosition(FALSE, boards[currentMove]);
14212     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14213     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14214         DisplayComment(currentMove - 1, commentList[currentMove]);
14215     }
14216 }
14217
14218
14219 void
14220 ForwardEvent ()
14221 {
14222     if (gameMode == IcsExamining && !pausing) {
14223         SendToICS(ics_prefix);
14224         SendToICS("forward\n");
14225     } else {
14226         ForwardInner(currentMove + 1);
14227     }
14228 }
14229
14230 void
14231 ToEndEvent ()
14232 {
14233     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14234         /* to optimze, we temporarily turn off analysis mode while we feed
14235          * the remaining moves to the engine. Otherwise we get analysis output
14236          * after each move.
14237          */
14238         if (first.analysisSupport) {
14239           SendToProgram("exit\nforce\n", &first);
14240           first.analyzing = FALSE;
14241         }
14242     }
14243
14244     if (gameMode == IcsExamining && !pausing) {
14245         SendToICS(ics_prefix);
14246         SendToICS("forward 999999\n");
14247     } else {
14248         ForwardInner(forwardMostMove);
14249     }
14250
14251     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14252         /* we have fed all the moves, so reactivate analysis mode */
14253         SendToProgram("analyze\n", &first);
14254         first.analyzing = TRUE;
14255         /*first.maybeThinking = TRUE;*/
14256         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14257     }
14258 }
14259
14260 void
14261 BackwardInner (int target)
14262 {
14263     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14264
14265     if (appData.debugMode)
14266         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14267                 target, currentMove, forwardMostMove);
14268
14269     if (gameMode == EditPosition) return;
14270     MarkTargetSquares(1);
14271     if (currentMove <= backwardMostMove) {
14272         ClearHighlights();
14273         DrawPosition(full_redraw, boards[currentMove]);
14274         return;
14275     }
14276     if (gameMode == PlayFromGameFile && !pausing)
14277       PauseEvent();
14278
14279     if (moveList[target][0]) {
14280         int fromX, fromY, toX, toY;
14281         toX = moveList[target][2] - AAA;
14282         toY = moveList[target][3] - ONE;
14283         if (moveList[target][1] == '@') {
14284             if (appData.highlightLastMove) {
14285                 SetHighlights(-1, -1, toX, toY);
14286             }
14287         } else {
14288             fromX = moveList[target][0] - AAA;
14289             fromY = moveList[target][1] - ONE;
14290             if (target == currentMove - 1) {
14291                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14292             }
14293             if (appData.highlightLastMove) {
14294                 SetHighlights(fromX, fromY, toX, toY);
14295             }
14296         }
14297     }
14298     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14299         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14300         while (currentMove > target) {
14301             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14302                 // null move cannot be undone. Reload program with move history before it.
14303                 int i;
14304                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14305                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14306                 }
14307                 SendBoard(&first, i); 
14308                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14309                 break;
14310             }
14311             SendToProgram("undo\n", &first);
14312             currentMove--;
14313         }
14314     } else {
14315         currentMove = target;
14316     }
14317
14318     if (gameMode == EditGame || gameMode == EndOfGame) {
14319         whiteTimeRemaining = timeRemaining[0][currentMove];
14320         blackTimeRemaining = timeRemaining[1][currentMove];
14321     }
14322     DisplayBothClocks();
14323     DisplayMove(currentMove - 1);
14324     DrawPosition(full_redraw, boards[currentMove]);
14325     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14326     // [HGM] PV info: routine tests if comment empty
14327     DisplayComment(currentMove - 1, commentList[currentMove]);
14328 }
14329
14330 void
14331 BackwardEvent ()
14332 {
14333     if (gameMode == IcsExamining && !pausing) {
14334         SendToICS(ics_prefix);
14335         SendToICS("backward\n");
14336     } else {
14337         BackwardInner(currentMove - 1);
14338     }
14339 }
14340
14341 void
14342 ToStartEvent ()
14343 {
14344     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14345         /* to optimize, we temporarily turn off analysis mode while we undo
14346          * all the moves. Otherwise we get analysis output after each undo.
14347          */
14348         if (first.analysisSupport) {
14349           SendToProgram("exit\nforce\n", &first);
14350           first.analyzing = FALSE;
14351         }
14352     }
14353
14354     if (gameMode == IcsExamining && !pausing) {
14355         SendToICS(ics_prefix);
14356         SendToICS("backward 999999\n");
14357     } else {
14358         BackwardInner(backwardMostMove);
14359     }
14360
14361     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14362         /* we have fed all the moves, so reactivate analysis mode */
14363         SendToProgram("analyze\n", &first);
14364         first.analyzing = TRUE;
14365         /*first.maybeThinking = TRUE;*/
14366         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14367     }
14368 }
14369
14370 void
14371 ToNrEvent (int to)
14372 {
14373   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14374   if (to >= forwardMostMove) to = forwardMostMove;
14375   if (to <= backwardMostMove) to = backwardMostMove;
14376   if (to < currentMove) {
14377     BackwardInner(to);
14378   } else {
14379     ForwardInner(to);
14380   }
14381 }
14382
14383 void
14384 RevertEvent (Boolean annotate)
14385 {
14386     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14387         return;
14388     }
14389     if (gameMode != IcsExamining) {
14390         DisplayError(_("You are not examining a game"), 0);
14391         return;
14392     }
14393     if (pausing) {
14394         DisplayError(_("You can't revert while pausing"), 0);
14395         return;
14396     }
14397     SendToICS(ics_prefix);
14398     SendToICS("revert\n");
14399 }
14400
14401 void
14402 RetractMoveEvent ()
14403 {
14404     switch (gameMode) {
14405       case MachinePlaysWhite:
14406       case MachinePlaysBlack:
14407         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14408             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14409             return;
14410         }
14411         if (forwardMostMove < 2) return;
14412         currentMove = forwardMostMove = forwardMostMove - 2;
14413         whiteTimeRemaining = timeRemaining[0][currentMove];
14414         blackTimeRemaining = timeRemaining[1][currentMove];
14415         DisplayBothClocks();
14416         DisplayMove(currentMove - 1);
14417         ClearHighlights();/*!! could figure this out*/
14418         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14419         SendToProgram("remove\n", &first);
14420         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14421         break;
14422
14423       case BeginningOfGame:
14424       default:
14425         break;
14426
14427       case IcsPlayingWhite:
14428       case IcsPlayingBlack:
14429         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14430             SendToICS(ics_prefix);
14431             SendToICS("takeback 2\n");
14432         } else {
14433             SendToICS(ics_prefix);
14434             SendToICS("takeback 1\n");
14435         }
14436         break;
14437     }
14438 }
14439
14440 void
14441 MoveNowEvent ()
14442 {
14443     ChessProgramState *cps;
14444
14445     switch (gameMode) {
14446       case MachinePlaysWhite:
14447         if (!WhiteOnMove(forwardMostMove)) {
14448             DisplayError(_("It is your turn"), 0);
14449             return;
14450         }
14451         cps = &first;
14452         break;
14453       case MachinePlaysBlack:
14454         if (WhiteOnMove(forwardMostMove)) {
14455             DisplayError(_("It is your turn"), 0);
14456             return;
14457         }
14458         cps = &first;
14459         break;
14460       case TwoMachinesPlay:
14461         if (WhiteOnMove(forwardMostMove) ==
14462             (first.twoMachinesColor[0] == 'w')) {
14463             cps = &first;
14464         } else {
14465             cps = &second;
14466         }
14467         break;
14468       case BeginningOfGame:
14469       default:
14470         return;
14471     }
14472     SendToProgram("?\n", cps);
14473 }
14474
14475 void
14476 TruncateGameEvent ()
14477 {
14478     EditGameEvent();
14479     if (gameMode != EditGame) return;
14480     TruncateGame();
14481 }
14482
14483 void
14484 TruncateGame ()
14485 {
14486     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14487     if (forwardMostMove > currentMove) {
14488         if (gameInfo.resultDetails != NULL) {
14489             free(gameInfo.resultDetails);
14490             gameInfo.resultDetails = NULL;
14491             gameInfo.result = GameUnfinished;
14492         }
14493         forwardMostMove = currentMove;
14494         HistorySet(parseList, backwardMostMove, forwardMostMove,
14495                    currentMove-1);
14496     }
14497 }
14498
14499 void
14500 HintEvent ()
14501 {
14502     if (appData.noChessProgram) return;
14503     switch (gameMode) {
14504       case MachinePlaysWhite:
14505         if (WhiteOnMove(forwardMostMove)) {
14506             DisplayError(_("Wait until your turn"), 0);
14507             return;
14508         }
14509         break;
14510       case BeginningOfGame:
14511       case MachinePlaysBlack:
14512         if (!WhiteOnMove(forwardMostMove)) {
14513             DisplayError(_("Wait until your turn"), 0);
14514             return;
14515         }
14516         break;
14517       default:
14518         DisplayError(_("No hint available"), 0);
14519         return;
14520     }
14521     SendToProgram("hint\n", &first);
14522     hintRequested = TRUE;
14523 }
14524
14525 void
14526 BookEvent ()
14527 {
14528     if (appData.noChessProgram) return;
14529     switch (gameMode) {
14530       case MachinePlaysWhite:
14531         if (WhiteOnMove(forwardMostMove)) {
14532             DisplayError(_("Wait until your turn"), 0);
14533             return;
14534         }
14535         break;
14536       case BeginningOfGame:
14537       case MachinePlaysBlack:
14538         if (!WhiteOnMove(forwardMostMove)) {
14539             DisplayError(_("Wait until your turn"), 0);
14540             return;
14541         }
14542         break;
14543       case EditPosition:
14544         EditPositionDone(TRUE);
14545         break;
14546       case TwoMachinesPlay:
14547         return;
14548       default:
14549         break;
14550     }
14551     SendToProgram("bk\n", &first);
14552     bookOutput[0] = NULLCHAR;
14553     bookRequested = TRUE;
14554 }
14555
14556 void
14557 AboutGameEvent ()
14558 {
14559     char *tags = PGNTags(&gameInfo);
14560     TagsPopUp(tags, CmailMsg());
14561     free(tags);
14562 }
14563
14564 /* end button procedures */
14565
14566 void
14567 PrintPosition (FILE *fp, int move)
14568 {
14569     int i, j;
14570
14571     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14572         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14573             char c = PieceToChar(boards[move][i][j]);
14574             fputc(c == 'x' ? '.' : c, fp);
14575             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14576         }
14577     }
14578     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14579       fprintf(fp, "white to play\n");
14580     else
14581       fprintf(fp, "black to play\n");
14582 }
14583
14584 void
14585 PrintOpponents (FILE *fp)
14586 {
14587     if (gameInfo.white != NULL) {
14588         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14589     } else {
14590         fprintf(fp, "\n");
14591     }
14592 }
14593
14594 /* Find last component of program's own name, using some heuristics */
14595 void
14596 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14597 {
14598     char *p, *q;
14599     int local = (strcmp(host, "localhost") == 0);
14600     while (!local && (p = strchr(prog, ';')) != NULL) {
14601         p++;
14602         while (*p == ' ') p++;
14603         prog = p;
14604     }
14605     if (*prog == '"' || *prog == '\'') {
14606         q = strchr(prog + 1, *prog);
14607     } else {
14608         q = strchr(prog, ' ');
14609     }
14610     if (q == NULL) q = prog + strlen(prog);
14611     p = q;
14612     while (p >= prog && *p != '/' && *p != '\\') p--;
14613     p++;
14614     if(p == prog && *p == '"') p++;
14615     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14616     memcpy(buf, p, q - p);
14617     buf[q - p] = NULLCHAR;
14618     if (!local) {
14619         strcat(buf, "@");
14620         strcat(buf, host);
14621     }
14622 }
14623
14624 char *
14625 TimeControlTagValue ()
14626 {
14627     char buf[MSG_SIZ];
14628     if (!appData.clockMode) {
14629       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14630     } else if (movesPerSession > 0) {
14631       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14632     } else if (timeIncrement == 0) {
14633       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14634     } else {
14635       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14636     }
14637     return StrSave(buf);
14638 }
14639
14640 void
14641 SetGameInfo ()
14642 {
14643     /* This routine is used only for certain modes */
14644     VariantClass v = gameInfo.variant;
14645     ChessMove r = GameUnfinished;
14646     char *p = NULL;
14647
14648     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14649         r = gameInfo.result;
14650         p = gameInfo.resultDetails;
14651         gameInfo.resultDetails = NULL;
14652     }
14653     ClearGameInfo(&gameInfo);
14654     gameInfo.variant = v;
14655
14656     switch (gameMode) {
14657       case MachinePlaysWhite:
14658         gameInfo.event = StrSave( appData.pgnEventHeader );
14659         gameInfo.site = StrSave(HostName());
14660         gameInfo.date = PGNDate();
14661         gameInfo.round = StrSave("-");
14662         gameInfo.white = StrSave(first.tidy);
14663         gameInfo.black = StrSave(UserName());
14664         gameInfo.timeControl = TimeControlTagValue();
14665         break;
14666
14667       case MachinePlaysBlack:
14668         gameInfo.event = StrSave( appData.pgnEventHeader );
14669         gameInfo.site = StrSave(HostName());
14670         gameInfo.date = PGNDate();
14671         gameInfo.round = StrSave("-");
14672         gameInfo.white = StrSave(UserName());
14673         gameInfo.black = StrSave(first.tidy);
14674         gameInfo.timeControl = TimeControlTagValue();
14675         break;
14676
14677       case TwoMachinesPlay:
14678         gameInfo.event = StrSave( appData.pgnEventHeader );
14679         gameInfo.site = StrSave(HostName());
14680         gameInfo.date = PGNDate();
14681         if (roundNr > 0) {
14682             char buf[MSG_SIZ];
14683             snprintf(buf, MSG_SIZ, "%d", roundNr);
14684             gameInfo.round = StrSave(buf);
14685         } else {
14686             gameInfo.round = StrSave("-");
14687         }
14688         if (first.twoMachinesColor[0] == 'w') {
14689             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14690             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14691         } else {
14692             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14693             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14694         }
14695         gameInfo.timeControl = TimeControlTagValue();
14696         break;
14697
14698       case EditGame:
14699         gameInfo.event = StrSave("Edited game");
14700         gameInfo.site = StrSave(HostName());
14701         gameInfo.date = PGNDate();
14702         gameInfo.round = StrSave("-");
14703         gameInfo.white = StrSave("-");
14704         gameInfo.black = StrSave("-");
14705         gameInfo.result = r;
14706         gameInfo.resultDetails = p;
14707         break;
14708
14709       case EditPosition:
14710         gameInfo.event = StrSave("Edited position");
14711         gameInfo.site = StrSave(HostName());
14712         gameInfo.date = PGNDate();
14713         gameInfo.round = StrSave("-");
14714         gameInfo.white = StrSave("-");
14715         gameInfo.black = StrSave("-");
14716         break;
14717
14718       case IcsPlayingWhite:
14719       case IcsPlayingBlack:
14720       case IcsObserving:
14721       case IcsExamining:
14722         break;
14723
14724       case PlayFromGameFile:
14725         gameInfo.event = StrSave("Game from non-PGN file");
14726         gameInfo.site = StrSave(HostName());
14727         gameInfo.date = PGNDate();
14728         gameInfo.round = StrSave("-");
14729         gameInfo.white = StrSave("?");
14730         gameInfo.black = StrSave("?");
14731         break;
14732
14733       default:
14734         break;
14735     }
14736 }
14737
14738 void
14739 ReplaceComment (int index, char *text)
14740 {
14741     int len;
14742     char *p;
14743     float score;
14744
14745     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14746        pvInfoList[index-1].depth == len &&
14747        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14748        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14749     while (*text == '\n') text++;
14750     len = strlen(text);
14751     while (len > 0 && text[len - 1] == '\n') len--;
14752
14753     if (commentList[index] != NULL)
14754       free(commentList[index]);
14755
14756     if (len == 0) {
14757         commentList[index] = NULL;
14758         return;
14759     }
14760   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14761       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14762       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14763     commentList[index] = (char *) malloc(len + 2);
14764     strncpy(commentList[index], text, len);
14765     commentList[index][len] = '\n';
14766     commentList[index][len + 1] = NULLCHAR;
14767   } else {
14768     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14769     char *p;
14770     commentList[index] = (char *) malloc(len + 7);
14771     safeStrCpy(commentList[index], "{\n", 3);
14772     safeStrCpy(commentList[index]+2, text, len+1);
14773     commentList[index][len+2] = NULLCHAR;
14774     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14775     strcat(commentList[index], "\n}\n");
14776   }
14777 }
14778
14779 void
14780 CrushCRs (char *text)
14781 {
14782   char *p = text;
14783   char *q = text;
14784   char ch;
14785
14786   do {
14787     ch = *p++;
14788     if (ch == '\r') continue;
14789     *q++ = ch;
14790   } while (ch != '\0');
14791 }
14792
14793 void
14794 AppendComment (int index, char *text, Boolean addBraces)
14795 /* addBraces  tells if we should add {} */
14796 {
14797     int oldlen, len;
14798     char *old;
14799
14800 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14801     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14802
14803     CrushCRs(text);
14804     while (*text == '\n') text++;
14805     len = strlen(text);
14806     while (len > 0 && text[len - 1] == '\n') len--;
14807     text[len] = NULLCHAR;
14808
14809     if (len == 0) return;
14810
14811     if (commentList[index] != NULL) {
14812       Boolean addClosingBrace = addBraces;
14813         old = commentList[index];
14814         oldlen = strlen(old);
14815         while(commentList[index][oldlen-1] ==  '\n')
14816           commentList[index][--oldlen] = NULLCHAR;
14817         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14818         safeStrCpy(commentList[index], old, oldlen + len + 6);
14819         free(old);
14820         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14821         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14822           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14823           while (*text == '\n') { text++; len--; }
14824           commentList[index][--oldlen] = NULLCHAR;
14825       }
14826         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14827         else          strcat(commentList[index], "\n");
14828         strcat(commentList[index], text);
14829         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14830         else          strcat(commentList[index], "\n");
14831     } else {
14832         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14833         if(addBraces)
14834           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14835         else commentList[index][0] = NULLCHAR;
14836         strcat(commentList[index], text);
14837         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14838         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14839     }
14840 }
14841
14842 static char *
14843 FindStr (char * text, char * sub_text)
14844 {
14845     char * result = strstr( text, sub_text );
14846
14847     if( result != NULL ) {
14848         result += strlen( sub_text );
14849     }
14850
14851     return result;
14852 }
14853
14854 /* [AS] Try to extract PV info from PGN comment */
14855 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14856 char *
14857 GetInfoFromComment (int index, char * text)
14858 {
14859     char * sep = text, *p;
14860
14861     if( text != NULL && index > 0 ) {
14862         int score = 0;
14863         int depth = 0;
14864         int time = -1, sec = 0, deci;
14865         char * s_eval = FindStr( text, "[%eval " );
14866         char * s_emt = FindStr( text, "[%emt " );
14867
14868         if( s_eval != NULL || s_emt != NULL ) {
14869             /* New style */
14870             char delim;
14871
14872             if( s_eval != NULL ) {
14873                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14874                     return text;
14875                 }
14876
14877                 if( delim != ']' ) {
14878                     return text;
14879                 }
14880             }
14881
14882             if( s_emt != NULL ) {
14883             }
14884                 return text;
14885         }
14886         else {
14887             /* We expect something like: [+|-]nnn.nn/dd */
14888             int score_lo = 0;
14889
14890             if(*text != '{') return text; // [HGM] braces: must be normal comment
14891
14892             sep = strchr( text, '/' );
14893             if( sep == NULL || sep < (text+4) ) {
14894                 return text;
14895             }
14896
14897             p = text;
14898             if(p[1] == '(') { // comment starts with PV
14899                p = strchr(p, ')'); // locate end of PV
14900                if(p == NULL || sep < p+5) return text;
14901                // at this point we have something like "{(.*) +0.23/6 ..."
14902                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14903                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14904                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14905             }
14906             time = -1; sec = -1; deci = -1;
14907             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14908                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14909                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14910                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14911                 return text;
14912             }
14913
14914             if( score_lo < 0 || score_lo >= 100 ) {
14915                 return text;
14916             }
14917
14918             if(sec >= 0) time = 600*time + 10*sec; else
14919             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14920
14921             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14922
14923             /* [HGM] PV time: now locate end of PV info */
14924             while( *++sep >= '0' && *sep <= '9'); // strip depth
14925             if(time >= 0)
14926             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14927             if(sec >= 0)
14928             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14929             if(deci >= 0)
14930             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14931             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
14932         }
14933
14934         if( depth <= 0 ) {
14935             return text;
14936         }
14937
14938         if( time < 0 ) {
14939             time = -1;
14940         }
14941
14942         pvInfoList[index-1].depth = depth;
14943         pvInfoList[index-1].score = score;
14944         pvInfoList[index-1].time  = 10*time; // centi-sec
14945         if(*sep == '}') *sep = 0; else *--sep = '{';
14946         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14947     }
14948     return sep;
14949 }
14950
14951 void
14952 SendToProgram (char *message, ChessProgramState *cps)
14953 {
14954     int count, outCount, error;
14955     char buf[MSG_SIZ];
14956
14957     if (cps->pr == NoProc) return;
14958     Attention(cps);
14959
14960     if (appData.debugMode) {
14961         TimeMark now;
14962         GetTimeMark(&now);
14963         fprintf(debugFP, "%ld >%-6s: %s",
14964                 SubtractTimeMarks(&now, &programStartTime),
14965                 cps->which, message);
14966     }
14967
14968     count = strlen(message);
14969     outCount = OutputToProcess(cps->pr, message, count, &error);
14970     if (outCount < count && !exiting
14971                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14972       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14973       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14974         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14975             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14976                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14977                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14978                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14979             } else {
14980                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14981                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14982                 gameInfo.result = res;
14983             }
14984             gameInfo.resultDetails = StrSave(buf);
14985         }
14986         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14987         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14988     }
14989 }
14990
14991 void
14992 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
14993 {
14994     char *end_str;
14995     char buf[MSG_SIZ];
14996     ChessProgramState *cps = (ChessProgramState *)closure;
14997
14998     if (isr != cps->isr) return; /* Killed intentionally */
14999     if (count <= 0) {
15000         if (count == 0) {
15001             RemoveInputSource(cps->isr);
15002             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15003             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15004                     _(cps->which), cps->program);
15005         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15006                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15007                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15008                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15009                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15010                 } else {
15011                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15012                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15013                     gameInfo.result = res;
15014                 }
15015                 gameInfo.resultDetails = StrSave(buf);
15016             }
15017             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15018             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15019         } else {
15020             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15021                     _(cps->which), cps->program);
15022             RemoveInputSource(cps->isr);
15023
15024             /* [AS] Program is misbehaving badly... kill it */
15025             if( count == -2 ) {
15026                 DestroyChildProcess( cps->pr, 9 );
15027                 cps->pr = NoProc;
15028             }
15029
15030             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15031         }
15032         return;
15033     }
15034
15035     if ((end_str = strchr(message, '\r')) != NULL)
15036       *end_str = NULLCHAR;
15037     if ((end_str = strchr(message, '\n')) != NULL)
15038       *end_str = NULLCHAR;
15039
15040     if (appData.debugMode) {
15041         TimeMark now; int print = 1;
15042         char *quote = ""; char c; int i;
15043
15044         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15045                 char start = message[0];
15046                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15047                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15048                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15049                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15050                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15051                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15052                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15053                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15054                    sscanf(message, "hint: %c", &c)!=1 && 
15055                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15056                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15057                     print = (appData.engineComments >= 2);
15058                 }
15059                 message[0] = start; // restore original message
15060         }
15061         if(print) {
15062                 GetTimeMark(&now);
15063                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15064                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15065                         quote,
15066                         message);
15067         }
15068     }
15069
15070     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15071     if (appData.icsEngineAnalyze) {
15072         if (strstr(message, "whisper") != NULL ||
15073              strstr(message, "kibitz") != NULL ||
15074             strstr(message, "tellics") != NULL) return;
15075     }
15076
15077     HandleMachineMove(message, cps);
15078 }
15079
15080
15081 void
15082 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15083 {
15084     char buf[MSG_SIZ];
15085     int seconds;
15086
15087     if( timeControl_2 > 0 ) {
15088         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15089             tc = timeControl_2;
15090         }
15091     }
15092     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15093     inc /= cps->timeOdds;
15094     st  /= cps->timeOdds;
15095
15096     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15097
15098     if (st > 0) {
15099       /* Set exact time per move, normally using st command */
15100       if (cps->stKludge) {
15101         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15102         seconds = st % 60;
15103         if (seconds == 0) {
15104           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15105         } else {
15106           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15107         }
15108       } else {
15109         snprintf(buf, MSG_SIZ, "st %d\n", st);
15110       }
15111     } else {
15112       /* Set conventional or incremental time control, using level command */
15113       if (seconds == 0) {
15114         /* Note old gnuchess bug -- minutes:seconds used to not work.
15115            Fixed in later versions, but still avoid :seconds
15116            when seconds is 0. */
15117         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15118       } else {
15119         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15120                  seconds, inc/1000.);
15121       }
15122     }
15123     SendToProgram(buf, cps);
15124
15125     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15126     /* Orthogonally, limit search to given depth */
15127     if (sd > 0) {
15128       if (cps->sdKludge) {
15129         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15130       } else {
15131         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15132       }
15133       SendToProgram(buf, cps);
15134     }
15135
15136     if(cps->nps >= 0) { /* [HGM] nps */
15137         if(cps->supportsNPS == FALSE)
15138           cps->nps = -1; // don't use if engine explicitly says not supported!
15139         else {
15140           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15141           SendToProgram(buf, cps);
15142         }
15143     }
15144 }
15145
15146 ChessProgramState *
15147 WhitePlayer ()
15148 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15149 {
15150     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15151        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15152         return &second;
15153     return &first;
15154 }
15155
15156 void
15157 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15158 {
15159     char message[MSG_SIZ];
15160     long time, otime;
15161
15162     /* Note: this routine must be called when the clocks are stopped
15163        or when they have *just* been set or switched; otherwise
15164        it will be off by the time since the current tick started.
15165     */
15166     if (machineWhite) {
15167         time = whiteTimeRemaining / 10;
15168         otime = blackTimeRemaining / 10;
15169     } else {
15170         time = blackTimeRemaining / 10;
15171         otime = whiteTimeRemaining / 10;
15172     }
15173     /* [HGM] translate opponent's time by time-odds factor */
15174     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15175     if (appData.debugMode) {
15176         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15177     }
15178
15179     if (time <= 0) time = 1;
15180     if (otime <= 0) otime = 1;
15181
15182     snprintf(message, MSG_SIZ, "time %ld\n", time);
15183     SendToProgram(message, cps);
15184
15185     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15186     SendToProgram(message, cps);
15187 }
15188
15189 int
15190 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15191 {
15192   char buf[MSG_SIZ];
15193   int len = strlen(name);
15194   int val;
15195
15196   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15197     (*p) += len + 1;
15198     sscanf(*p, "%d", &val);
15199     *loc = (val != 0);
15200     while (**p && **p != ' ')
15201       (*p)++;
15202     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15203     SendToProgram(buf, cps);
15204     return TRUE;
15205   }
15206   return FALSE;
15207 }
15208
15209 int
15210 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15211 {
15212   char buf[MSG_SIZ];
15213   int len = strlen(name);
15214   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15215     (*p) += len + 1;
15216     sscanf(*p, "%d", loc);
15217     while (**p && **p != ' ') (*p)++;
15218     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15219     SendToProgram(buf, cps);
15220     return TRUE;
15221   }
15222   return FALSE;
15223 }
15224
15225 int
15226 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15227 {
15228   char buf[MSG_SIZ];
15229   int len = strlen(name);
15230   if (strncmp((*p), name, len) == 0
15231       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15232     (*p) += len + 2;
15233     sscanf(*p, "%[^\"]", loc);
15234     while (**p && **p != '\"') (*p)++;
15235     if (**p == '\"') (*p)++;
15236     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15237     SendToProgram(buf, cps);
15238     return TRUE;
15239   }
15240   return FALSE;
15241 }
15242
15243 int
15244 ParseOption (Option *opt, ChessProgramState *cps)
15245 // [HGM] options: process the string that defines an engine option, and determine
15246 // name, type, default value, and allowed value range
15247 {
15248         char *p, *q, buf[MSG_SIZ];
15249         int n, min = (-1)<<31, max = 1<<31, def;
15250
15251         if(p = strstr(opt->name, " -spin ")) {
15252             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15253             if(max < min) max = min; // enforce consistency
15254             if(def < min) def = min;
15255             if(def > max) def = max;
15256             opt->value = def;
15257             opt->min = min;
15258             opt->max = max;
15259             opt->type = Spin;
15260         } else if((p = strstr(opt->name, " -slider "))) {
15261             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15262             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15263             if(max < min) max = min; // enforce consistency
15264             if(def < min) def = min;
15265             if(def > max) def = max;
15266             opt->value = def;
15267             opt->min = min;
15268             opt->max = max;
15269             opt->type = Spin; // Slider;
15270         } else if((p = strstr(opt->name, " -string "))) {
15271             opt->textValue = p+9;
15272             opt->type = TextBox;
15273         } else if((p = strstr(opt->name, " -file "))) {
15274             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15275             opt->textValue = p+7;
15276             opt->type = FileName; // FileName;
15277         } else if((p = strstr(opt->name, " -path "))) {
15278             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15279             opt->textValue = p+7;
15280             opt->type = PathName; // PathName;
15281         } else if(p = strstr(opt->name, " -check ")) {
15282             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15283             opt->value = (def != 0);
15284             opt->type = CheckBox;
15285         } else if(p = strstr(opt->name, " -combo ")) {
15286             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15287             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15288             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15289             opt->value = n = 0;
15290             while(q = StrStr(q, " /// ")) {
15291                 n++; *q = 0;    // count choices, and null-terminate each of them
15292                 q += 5;
15293                 if(*q == '*') { // remember default, which is marked with * prefix
15294                     q++;
15295                     opt->value = n;
15296                 }
15297                 cps->comboList[cps->comboCnt++] = q;
15298             }
15299             cps->comboList[cps->comboCnt++] = NULL;
15300             opt->max = n + 1;
15301             opt->type = ComboBox;
15302         } else if(p = strstr(opt->name, " -button")) {
15303             opt->type = Button;
15304         } else if(p = strstr(opt->name, " -save")) {
15305             opt->type = SaveButton;
15306         } else return FALSE;
15307         *p = 0; // terminate option name
15308         // now look if the command-line options define a setting for this engine option.
15309         if(cps->optionSettings && cps->optionSettings[0])
15310             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15311         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15312           snprintf(buf, MSG_SIZ, "option %s", p);
15313                 if(p = strstr(buf, ",")) *p = 0;
15314                 if(q = strchr(buf, '=')) switch(opt->type) {
15315                     case ComboBox:
15316                         for(n=0; n<opt->max; n++)
15317                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15318                         break;
15319                     case TextBox:
15320                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15321                         break;
15322                     case Spin:
15323                     case CheckBox:
15324                         opt->value = atoi(q+1);
15325                     default:
15326                         break;
15327                 }
15328                 strcat(buf, "\n");
15329                 SendToProgram(buf, cps);
15330         }
15331         return TRUE;
15332 }
15333
15334 void
15335 FeatureDone (ChessProgramState *cps, int val)
15336 {
15337   DelayedEventCallback cb = GetDelayedEvent();
15338   if ((cb == InitBackEnd3 && cps == &first) ||
15339       (cb == SettingsMenuIfReady && cps == &second) ||
15340       (cb == LoadEngine) ||
15341       (cb == TwoMachinesEventIfReady)) {
15342     CancelDelayedEvent();
15343     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15344   }
15345   cps->initDone = val;
15346 }
15347
15348 /* Parse feature command from engine */
15349 void
15350 ParseFeatures (char *args, ChessProgramState *cps)
15351 {
15352   char *p = args;
15353   char *q;
15354   int val;
15355   char buf[MSG_SIZ];
15356
15357   for (;;) {
15358     while (*p == ' ') p++;
15359     if (*p == NULLCHAR) return;
15360
15361     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15362     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15363     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15364     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15365     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15366     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15367     if (BoolFeature(&p, "reuse", &val, cps)) {
15368       /* Engine can disable reuse, but can't enable it if user said no */
15369       if (!val) cps->reuse = FALSE;
15370       continue;
15371     }
15372     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15373     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15374       if (gameMode == TwoMachinesPlay) {
15375         DisplayTwoMachinesTitle();
15376       } else {
15377         DisplayTitle("");
15378       }
15379       continue;
15380     }
15381     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15382     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15383     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15384     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15385     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15386     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15387     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15388     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15389     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15390     if (IntFeature(&p, "done", &val, cps)) {
15391       FeatureDone(cps, val);
15392       continue;
15393     }
15394     /* Added by Tord: */
15395     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15396     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15397     /* End of additions by Tord */
15398
15399     /* [HGM] added features: */
15400     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15401     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15402     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15403     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15404     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15405     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15406     if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15407         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15408           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15409             SendToProgram(buf, cps);
15410             continue;
15411         }
15412         if(cps->nrOptions >= MAX_OPTIONS) {
15413             cps->nrOptions--;
15414             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15415             DisplayError(buf, 0);
15416         }
15417         continue;
15418     }
15419     /* End of additions by HGM */
15420
15421     /* unknown feature: complain and skip */
15422     q = p;
15423     while (*q && *q != '=') q++;
15424     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15425     SendToProgram(buf, cps);
15426     p = q;
15427     if (*p == '=') {
15428       p++;
15429       if (*p == '\"') {
15430         p++;
15431         while (*p && *p != '\"') p++;
15432         if (*p == '\"') p++;
15433       } else {
15434         while (*p && *p != ' ') p++;
15435       }
15436     }
15437   }
15438
15439 }
15440
15441 void
15442 PeriodicUpdatesEvent (int newState)
15443 {
15444     if (newState == appData.periodicUpdates)
15445       return;
15446
15447     appData.periodicUpdates=newState;
15448
15449     /* Display type changes, so update it now */
15450 //    DisplayAnalysis();
15451
15452     /* Get the ball rolling again... */
15453     if (newState) {
15454         AnalysisPeriodicEvent(1);
15455         StartAnalysisClock();
15456     }
15457 }
15458
15459 void
15460 PonderNextMoveEvent (int newState)
15461 {
15462     if (newState == appData.ponderNextMove) return;
15463     if (gameMode == EditPosition) EditPositionDone(TRUE);
15464     if (newState) {
15465         SendToProgram("hard\n", &first);
15466         if (gameMode == TwoMachinesPlay) {
15467             SendToProgram("hard\n", &second);
15468         }
15469     } else {
15470         SendToProgram("easy\n", &first);
15471         thinkOutput[0] = NULLCHAR;
15472         if (gameMode == TwoMachinesPlay) {
15473             SendToProgram("easy\n", &second);
15474         }
15475     }
15476     appData.ponderNextMove = newState;
15477 }
15478
15479 void
15480 NewSettingEvent (int option, int *feature, char *command, int value)
15481 {
15482     char buf[MSG_SIZ];
15483
15484     if (gameMode == EditPosition) EditPositionDone(TRUE);
15485     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15486     if(feature == NULL || *feature) SendToProgram(buf, &first);
15487     if (gameMode == TwoMachinesPlay) {
15488         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15489     }
15490 }
15491
15492 void
15493 ShowThinkingEvent ()
15494 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15495 {
15496     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15497     int newState = appData.showThinking
15498         // [HGM] thinking: other features now need thinking output as well
15499         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15500
15501     if (oldState == newState) return;
15502     oldState = newState;
15503     if (gameMode == EditPosition) EditPositionDone(TRUE);
15504     if (oldState) {
15505         SendToProgram("post\n", &first);
15506         if (gameMode == TwoMachinesPlay) {
15507             SendToProgram("post\n", &second);
15508         }
15509     } else {
15510         SendToProgram("nopost\n", &first);
15511         thinkOutput[0] = NULLCHAR;
15512         if (gameMode == TwoMachinesPlay) {
15513             SendToProgram("nopost\n", &second);
15514         }
15515     }
15516 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15517 }
15518
15519 void
15520 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15521 {
15522   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15523   if (pr == NoProc) return;
15524   AskQuestion(title, question, replyPrefix, pr);
15525 }
15526
15527 void
15528 TypeInEvent (char firstChar)
15529 {
15530     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15531         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15532         gameMode == AnalyzeMode || gameMode == EditGame || 
15533         gameMode == EditPosition || gameMode == IcsExamining ||
15534         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15535         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15536                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15537                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15538         gameMode == Training) PopUpMoveDialog(firstChar);
15539 }
15540
15541 void
15542 TypeInDoneEvent (char *move)
15543 {
15544         Board board;
15545         int n, fromX, fromY, toX, toY;
15546         char promoChar;
15547         ChessMove moveType;
15548
15549         // [HGM] FENedit
15550         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15551                 EditPositionPasteFEN(move);
15552                 return;
15553         }
15554         // [HGM] movenum: allow move number to be typed in any mode
15555         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15556           ToNrEvent(2*n-1);
15557           return;
15558         }
15559
15560       if (gameMode != EditGame && currentMove != forwardMostMove && 
15561         gameMode != Training) {
15562         DisplayMoveError(_("Displayed move is not current"));
15563       } else {
15564         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15565           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15566         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15567         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15568           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15569           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15570         } else {
15571           DisplayMoveError(_("Could not parse move"));
15572         }
15573       }
15574 }
15575
15576 void
15577 DisplayMove (int moveNumber)
15578 {
15579     char message[MSG_SIZ];
15580     char res[MSG_SIZ];
15581     char cpThinkOutput[MSG_SIZ];
15582
15583     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15584
15585     if (moveNumber == forwardMostMove - 1 ||
15586         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15587
15588         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15589
15590         if (strchr(cpThinkOutput, '\n')) {
15591             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15592         }
15593     } else {
15594         *cpThinkOutput = NULLCHAR;
15595     }
15596
15597     /* [AS] Hide thinking from human user */
15598     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15599         *cpThinkOutput = NULLCHAR;
15600         if( thinkOutput[0] != NULLCHAR ) {
15601             int i;
15602
15603             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15604                 cpThinkOutput[i] = '.';
15605             }
15606             cpThinkOutput[i] = NULLCHAR;
15607             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15608         }
15609     }
15610
15611     if (moveNumber == forwardMostMove - 1 &&
15612         gameInfo.resultDetails != NULL) {
15613         if (gameInfo.resultDetails[0] == NULLCHAR) {
15614           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15615         } else {
15616           snprintf(res, MSG_SIZ, " {%s} %s",
15617                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15618         }
15619     } else {
15620         res[0] = NULLCHAR;
15621     }
15622
15623     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15624         DisplayMessage(res, cpThinkOutput);
15625     } else {
15626       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15627                 WhiteOnMove(moveNumber) ? " " : ".. ",
15628                 parseList[moveNumber], res);
15629         DisplayMessage(message, cpThinkOutput);
15630     }
15631 }
15632
15633 void
15634 DisplayComment (int moveNumber, char *text)
15635 {
15636     char title[MSG_SIZ];
15637
15638     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15639       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15640     } else {
15641       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15642               WhiteOnMove(moveNumber) ? " " : ".. ",
15643               parseList[moveNumber]);
15644     }
15645     if (text != NULL && (appData.autoDisplayComment || commentUp))
15646         CommentPopUp(title, text);
15647 }
15648
15649 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15650  * might be busy thinking or pondering.  It can be omitted if your
15651  * gnuchess is configured to stop thinking immediately on any user
15652  * input.  However, that gnuchess feature depends on the FIONREAD
15653  * ioctl, which does not work properly on some flavors of Unix.
15654  */
15655 void
15656 Attention (ChessProgramState *cps)
15657 {
15658 #if ATTENTION
15659     if (!cps->useSigint) return;
15660     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15661     switch (gameMode) {
15662       case MachinePlaysWhite:
15663       case MachinePlaysBlack:
15664       case TwoMachinesPlay:
15665       case IcsPlayingWhite:
15666       case IcsPlayingBlack:
15667       case AnalyzeMode:
15668       case AnalyzeFile:
15669         /* Skip if we know it isn't thinking */
15670         if (!cps->maybeThinking) return;
15671         if (appData.debugMode)
15672           fprintf(debugFP, "Interrupting %s\n", cps->which);
15673         InterruptChildProcess(cps->pr);
15674         cps->maybeThinking = FALSE;
15675         break;
15676       default:
15677         break;
15678     }
15679 #endif /*ATTENTION*/
15680 }
15681
15682 int
15683 CheckFlags ()
15684 {
15685     if (whiteTimeRemaining <= 0) {
15686         if (!whiteFlag) {
15687             whiteFlag = TRUE;
15688             if (appData.icsActive) {
15689                 if (appData.autoCallFlag &&
15690                     gameMode == IcsPlayingBlack && !blackFlag) {
15691                   SendToICS(ics_prefix);
15692                   SendToICS("flag\n");
15693                 }
15694             } else {
15695                 if (blackFlag) {
15696                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15697                 } else {
15698                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15699                     if (appData.autoCallFlag) {
15700                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15701                         return TRUE;
15702                     }
15703                 }
15704             }
15705         }
15706     }
15707     if (blackTimeRemaining <= 0) {
15708         if (!blackFlag) {
15709             blackFlag = TRUE;
15710             if (appData.icsActive) {
15711                 if (appData.autoCallFlag &&
15712                     gameMode == IcsPlayingWhite && !whiteFlag) {
15713                   SendToICS(ics_prefix);
15714                   SendToICS("flag\n");
15715                 }
15716             } else {
15717                 if (whiteFlag) {
15718                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15719                 } else {
15720                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15721                     if (appData.autoCallFlag) {
15722                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15723                         return TRUE;
15724                     }
15725                 }
15726             }
15727         }
15728     }
15729     return FALSE;
15730 }
15731
15732 void
15733 CheckTimeControl ()
15734 {
15735     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15736         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15737
15738     /*
15739      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15740      */
15741     if ( !WhiteOnMove(forwardMostMove) ) {
15742         /* White made time control */
15743         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15744         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15745         /* [HGM] time odds: correct new time quota for time odds! */
15746                                             / WhitePlayer()->timeOdds;
15747         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15748     } else {
15749         lastBlack -= blackTimeRemaining;
15750         /* Black made time control */
15751         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15752                                             / WhitePlayer()->other->timeOdds;
15753         lastWhite = whiteTimeRemaining;
15754     }
15755 }
15756
15757 void
15758 DisplayBothClocks ()
15759 {
15760     int wom = gameMode == EditPosition ?
15761       !blackPlaysFirst : WhiteOnMove(currentMove);
15762     DisplayWhiteClock(whiteTimeRemaining, wom);
15763     DisplayBlackClock(blackTimeRemaining, !wom);
15764 }
15765
15766
15767 /* Timekeeping seems to be a portability nightmare.  I think everyone
15768    has ftime(), but I'm really not sure, so I'm including some ifdefs
15769    to use other calls if you don't.  Clocks will be less accurate if
15770    you have neither ftime nor gettimeofday.
15771 */
15772
15773 /* VS 2008 requires the #include outside of the function */
15774 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15775 #include <sys/timeb.h>
15776 #endif
15777
15778 /* Get the current time as a TimeMark */
15779 void
15780 GetTimeMark (TimeMark *tm)
15781 {
15782 #if HAVE_GETTIMEOFDAY
15783
15784     struct timeval timeVal;
15785     struct timezone timeZone;
15786
15787     gettimeofday(&timeVal, &timeZone);
15788     tm->sec = (long) timeVal.tv_sec;
15789     tm->ms = (int) (timeVal.tv_usec / 1000L);
15790
15791 #else /*!HAVE_GETTIMEOFDAY*/
15792 #if HAVE_FTIME
15793
15794 // include <sys/timeb.h> / moved to just above start of function
15795     struct timeb timeB;
15796
15797     ftime(&timeB);
15798     tm->sec = (long) timeB.time;
15799     tm->ms = (int) timeB.millitm;
15800
15801 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15802     tm->sec = (long) time(NULL);
15803     tm->ms = 0;
15804 #endif
15805 #endif
15806 }
15807
15808 /* Return the difference in milliseconds between two
15809    time marks.  We assume the difference will fit in a long!
15810 */
15811 long
15812 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
15813 {
15814     return 1000L*(tm2->sec - tm1->sec) +
15815            (long) (tm2->ms - tm1->ms);
15816 }
15817
15818
15819 /*
15820  * Code to manage the game clocks.
15821  *
15822  * In tournament play, black starts the clock and then white makes a move.
15823  * We give the human user a slight advantage if he is playing white---the
15824  * clocks don't run until he makes his first move, so it takes zero time.
15825  * Also, we don't account for network lag, so we could get out of sync
15826  * with GNU Chess's clock -- but then, referees are always right.
15827  */
15828
15829 static TimeMark tickStartTM;
15830 static long intendedTickLength;
15831
15832 long
15833 NextTickLength (long timeRemaining)
15834 {
15835     long nominalTickLength, nextTickLength;
15836
15837     if (timeRemaining > 0L && timeRemaining <= 10000L)
15838       nominalTickLength = 100L;
15839     else
15840       nominalTickLength = 1000L;
15841     nextTickLength = timeRemaining % nominalTickLength;
15842     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15843
15844     return nextTickLength;
15845 }
15846
15847 /* Adjust clock one minute up or down */
15848 void
15849 AdjustClock (Boolean which, int dir)
15850 {
15851     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15852     if(which) blackTimeRemaining += 60000*dir;
15853     else      whiteTimeRemaining += 60000*dir;
15854     DisplayBothClocks();
15855     adjustedClock = TRUE;
15856 }
15857
15858 /* Stop clocks and reset to a fresh time control */
15859 void
15860 ResetClocks ()
15861 {
15862     (void) StopClockTimer();
15863     if (appData.icsActive) {
15864         whiteTimeRemaining = blackTimeRemaining = 0;
15865     } else if (searchTime) {
15866         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15867         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15868     } else { /* [HGM] correct new time quote for time odds */
15869         whiteTC = blackTC = fullTimeControlString;
15870         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15871         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15872     }
15873     if (whiteFlag || blackFlag) {
15874         DisplayTitle("");
15875         whiteFlag = blackFlag = FALSE;
15876     }
15877     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15878     DisplayBothClocks();
15879     adjustedClock = FALSE;
15880 }
15881
15882 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15883
15884 /* Decrement running clock by amount of time that has passed */
15885 void
15886 DecrementClocks ()
15887 {
15888     long timeRemaining;
15889     long lastTickLength, fudge;
15890     TimeMark now;
15891
15892     if (!appData.clockMode) return;
15893     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15894
15895     GetTimeMark(&now);
15896
15897     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15898
15899     /* Fudge if we woke up a little too soon */
15900     fudge = intendedTickLength - lastTickLength;
15901     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15902
15903     if (WhiteOnMove(forwardMostMove)) {
15904         if(whiteNPS >= 0) lastTickLength = 0;
15905         timeRemaining = whiteTimeRemaining -= lastTickLength;
15906         if(timeRemaining < 0 && !appData.icsActive) {
15907             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15908             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15909                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15910                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15911             }
15912         }
15913         DisplayWhiteClock(whiteTimeRemaining - fudge,
15914                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15915     } else {
15916         if(blackNPS >= 0) lastTickLength = 0;
15917         timeRemaining = blackTimeRemaining -= lastTickLength;
15918         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15919             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15920             if(suddenDeath) {
15921                 blackStartMove = forwardMostMove;
15922                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15923             }
15924         }
15925         DisplayBlackClock(blackTimeRemaining - fudge,
15926                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15927     }
15928     if (CheckFlags()) return;
15929
15930     tickStartTM = now;
15931     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15932     StartClockTimer(intendedTickLength);
15933
15934     /* if the time remaining has fallen below the alarm threshold, sound the
15935      * alarm. if the alarm has sounded and (due to a takeback or time control
15936      * with increment) the time remaining has increased to a level above the
15937      * threshold, reset the alarm so it can sound again.
15938      */
15939
15940     if (appData.icsActive && appData.icsAlarm) {
15941
15942         /* make sure we are dealing with the user's clock */
15943         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15944                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15945            )) return;
15946
15947         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15948             alarmSounded = FALSE;
15949         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15950             PlayAlarmSound();
15951             alarmSounded = TRUE;
15952         }
15953     }
15954 }
15955
15956
15957 /* A player has just moved, so stop the previously running
15958    clock and (if in clock mode) start the other one.
15959    We redisplay both clocks in case we're in ICS mode, because
15960    ICS gives us an update to both clocks after every move.
15961    Note that this routine is called *after* forwardMostMove
15962    is updated, so the last fractional tick must be subtracted
15963    from the color that is *not* on move now.
15964 */
15965 void
15966 SwitchClocks (int newMoveNr)
15967 {
15968     long lastTickLength;
15969     TimeMark now;
15970     int flagged = FALSE;
15971
15972     GetTimeMark(&now);
15973
15974     if (StopClockTimer() && appData.clockMode) {
15975         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15976         if (!WhiteOnMove(forwardMostMove)) {
15977             if(blackNPS >= 0) lastTickLength = 0;
15978             blackTimeRemaining -= lastTickLength;
15979            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15980 //         if(pvInfoList[forwardMostMove].time == -1)
15981                  pvInfoList[forwardMostMove].time =               // use GUI time
15982                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15983         } else {
15984            if(whiteNPS >= 0) lastTickLength = 0;
15985            whiteTimeRemaining -= lastTickLength;
15986            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15987 //         if(pvInfoList[forwardMostMove].time == -1)
15988                  pvInfoList[forwardMostMove].time =
15989                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15990         }
15991         flagged = CheckFlags();
15992     }
15993     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15994     CheckTimeControl();
15995
15996     if (flagged || !appData.clockMode) return;
15997
15998     switch (gameMode) {
15999       case MachinePlaysBlack:
16000       case MachinePlaysWhite:
16001       case BeginningOfGame:
16002         if (pausing) return;
16003         break;
16004
16005       case EditGame:
16006       case PlayFromGameFile:
16007       case IcsExamining:
16008         return;
16009
16010       default:
16011         break;
16012     }
16013
16014     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16015         if(WhiteOnMove(forwardMostMove))
16016              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16017         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16018     }
16019
16020     tickStartTM = now;
16021     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16022       whiteTimeRemaining : blackTimeRemaining);
16023     StartClockTimer(intendedTickLength);
16024 }
16025
16026
16027 /* Stop both clocks */
16028 void
16029 StopClocks ()
16030 {
16031     long lastTickLength;
16032     TimeMark now;
16033
16034     if (!StopClockTimer()) return;
16035     if (!appData.clockMode) return;
16036
16037     GetTimeMark(&now);
16038
16039     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16040     if (WhiteOnMove(forwardMostMove)) {
16041         if(whiteNPS >= 0) lastTickLength = 0;
16042         whiteTimeRemaining -= lastTickLength;
16043         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16044     } else {
16045         if(blackNPS >= 0) lastTickLength = 0;
16046         blackTimeRemaining -= lastTickLength;
16047         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16048     }
16049     CheckFlags();
16050 }
16051
16052 /* Start clock of player on move.  Time may have been reset, so
16053    if clock is already running, stop and restart it. */
16054 void
16055 StartClocks ()
16056 {
16057     (void) StopClockTimer(); /* in case it was running already */
16058     DisplayBothClocks();
16059     if (CheckFlags()) return;
16060
16061     if (!appData.clockMode) return;
16062     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16063
16064     GetTimeMark(&tickStartTM);
16065     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16066       whiteTimeRemaining : blackTimeRemaining);
16067
16068    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16069     whiteNPS = blackNPS = -1;
16070     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16071        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16072         whiteNPS = first.nps;
16073     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16074        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16075         blackNPS = first.nps;
16076     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16077         whiteNPS = second.nps;
16078     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16079         blackNPS = second.nps;
16080     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16081
16082     StartClockTimer(intendedTickLength);
16083 }
16084
16085 char *
16086 TimeString (long ms)
16087 {
16088     long second, minute, hour, day;
16089     char *sign = "";
16090     static char buf[32];
16091
16092     if (ms > 0 && ms <= 9900) {
16093       /* convert milliseconds to tenths, rounding up */
16094       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16095
16096       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16097       return buf;
16098     }
16099
16100     /* convert milliseconds to seconds, rounding up */
16101     /* use floating point to avoid strangeness of integer division
16102        with negative dividends on many machines */
16103     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16104
16105     if (second < 0) {
16106         sign = "-";
16107         second = -second;
16108     }
16109
16110     day = second / (60 * 60 * 24);
16111     second = second % (60 * 60 * 24);
16112     hour = second / (60 * 60);
16113     second = second % (60 * 60);
16114     minute = second / 60;
16115     second = second % 60;
16116
16117     if (day > 0)
16118       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16119               sign, day, hour, minute, second);
16120     else if (hour > 0)
16121       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16122     else
16123       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16124
16125     return buf;
16126 }
16127
16128
16129 /*
16130  * This is necessary because some C libraries aren't ANSI C compliant yet.
16131  */
16132 char *
16133 StrStr (char *string, char *match)
16134 {
16135     int i, length;
16136
16137     length = strlen(match);
16138
16139     for (i = strlen(string) - length; i >= 0; i--, string++)
16140       if (!strncmp(match, string, length))
16141         return string;
16142
16143     return NULL;
16144 }
16145
16146 char *
16147 StrCaseStr (char *string, char *match)
16148 {
16149     int i, j, length;
16150
16151     length = strlen(match);
16152
16153     for (i = strlen(string) - length; i >= 0; i--, string++) {
16154         for (j = 0; j < length; j++) {
16155             if (ToLower(match[j]) != ToLower(string[j]))
16156               break;
16157         }
16158         if (j == length) return string;
16159     }
16160
16161     return NULL;
16162 }
16163
16164 #ifndef _amigados
16165 int
16166 StrCaseCmp (char *s1, char *s2)
16167 {
16168     char c1, c2;
16169
16170     for (;;) {
16171         c1 = ToLower(*s1++);
16172         c2 = ToLower(*s2++);
16173         if (c1 > c2) return 1;
16174         if (c1 < c2) return -1;
16175         if (c1 == NULLCHAR) return 0;
16176     }
16177 }
16178
16179
16180 int
16181 ToLower (int c)
16182 {
16183     return isupper(c) ? tolower(c) : c;
16184 }
16185
16186
16187 int
16188 ToUpper (int c)
16189 {
16190     return islower(c) ? toupper(c) : c;
16191 }
16192 #endif /* !_amigados    */
16193
16194 char *
16195 StrSave (char *s)
16196 {
16197   char *ret;
16198
16199   if ((ret = (char *) malloc(strlen(s) + 1)))
16200     {
16201       safeStrCpy(ret, s, strlen(s)+1);
16202     }
16203   return ret;
16204 }
16205
16206 char *
16207 StrSavePtr (char *s, char **savePtr)
16208 {
16209     if (*savePtr) {
16210         free(*savePtr);
16211     }
16212     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16213       safeStrCpy(*savePtr, s, strlen(s)+1);
16214     }
16215     return(*savePtr);
16216 }
16217
16218 char *
16219 PGNDate ()
16220 {
16221     time_t clock;
16222     struct tm *tm;
16223     char buf[MSG_SIZ];
16224
16225     clock = time((time_t *)NULL);
16226     tm = localtime(&clock);
16227     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16228             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16229     return StrSave(buf);
16230 }
16231
16232
16233 char *
16234 PositionToFEN (int move, char *overrideCastling)
16235 {
16236     int i, j, fromX, fromY, toX, toY;
16237     int whiteToPlay;
16238     char buf[MSG_SIZ];
16239     char *p, *q;
16240     int emptycount;
16241     ChessSquare piece;
16242
16243     whiteToPlay = (gameMode == EditPosition) ?
16244       !blackPlaysFirst : (move % 2 == 0);
16245     p = buf;
16246
16247     /* Piece placement data */
16248     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16249         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16250         emptycount = 0;
16251         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16252             if (boards[move][i][j] == EmptySquare) {
16253                 emptycount++;
16254             } else { ChessSquare piece = boards[move][i][j];
16255                 if (emptycount > 0) {
16256                     if(emptycount<10) /* [HGM] can be >= 10 */
16257                         *p++ = '0' + emptycount;
16258                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16259                     emptycount = 0;
16260                 }
16261                 if(PieceToChar(piece) == '+') {
16262                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16263                     *p++ = '+';
16264                     piece = (ChessSquare)(DEMOTED piece);
16265                 }
16266                 *p++ = PieceToChar(piece);
16267                 if(p[-1] == '~') {
16268                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16269                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16270                     *p++ = '~';
16271                 }
16272             }
16273         }
16274         if (emptycount > 0) {
16275             if(emptycount<10) /* [HGM] can be >= 10 */
16276                 *p++ = '0' + emptycount;
16277             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16278             emptycount = 0;
16279         }
16280         *p++ = '/';
16281     }
16282     *(p - 1) = ' ';
16283
16284     /* [HGM] print Crazyhouse or Shogi holdings */
16285     if( gameInfo.holdingsWidth ) {
16286         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16287         q = p;
16288         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16289             piece = boards[move][i][BOARD_WIDTH-1];
16290             if( piece != EmptySquare )
16291               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16292                   *p++ = PieceToChar(piece);
16293         }
16294         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16295             piece = boards[move][BOARD_HEIGHT-i-1][0];
16296             if( piece != EmptySquare )
16297               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16298                   *p++ = PieceToChar(piece);
16299         }
16300
16301         if( q == p ) *p++ = '-';
16302         *p++ = ']';
16303         *p++ = ' ';
16304     }
16305
16306     /* Active color */
16307     *p++ = whiteToPlay ? 'w' : 'b';
16308     *p++ = ' ';
16309
16310   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16311     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16312   } else {
16313   if(nrCastlingRights) {
16314      q = p;
16315      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16316        /* [HGM] write directly from rights */
16317            if(boards[move][CASTLING][2] != NoRights &&
16318               boards[move][CASTLING][0] != NoRights   )
16319                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16320            if(boards[move][CASTLING][2] != NoRights &&
16321               boards[move][CASTLING][1] != NoRights   )
16322                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16323            if(boards[move][CASTLING][5] != NoRights &&
16324               boards[move][CASTLING][3] != NoRights   )
16325                 *p++ = boards[move][CASTLING][3] + AAA;
16326            if(boards[move][CASTLING][5] != NoRights &&
16327               boards[move][CASTLING][4] != NoRights   )
16328                 *p++ = boards[move][CASTLING][4] + AAA;
16329      } else {
16330
16331         /* [HGM] write true castling rights */
16332         if( nrCastlingRights == 6 ) {
16333             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16334                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16335             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16336                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16337             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16338                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16339             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16340                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16341         }
16342      }
16343      if (q == p) *p++ = '-'; /* No castling rights */
16344      *p++ = ' ';
16345   }
16346
16347   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16348      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16349     /* En passant target square */
16350     if (move > backwardMostMove) {
16351         fromX = moveList[move - 1][0] - AAA;
16352         fromY = moveList[move - 1][1] - ONE;
16353         toX = moveList[move - 1][2] - AAA;
16354         toY = moveList[move - 1][3] - ONE;
16355         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16356             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16357             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16358             fromX == toX) {
16359             /* 2-square pawn move just happened */
16360             *p++ = toX + AAA;
16361             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16362         } else {
16363             *p++ = '-';
16364         }
16365     } else if(move == backwardMostMove) {
16366         // [HGM] perhaps we should always do it like this, and forget the above?
16367         if((signed char)boards[move][EP_STATUS] >= 0) {
16368             *p++ = boards[move][EP_STATUS] + AAA;
16369             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16370         } else {
16371             *p++ = '-';
16372         }
16373     } else {
16374         *p++ = '-';
16375     }
16376     *p++ = ' ';
16377   }
16378   }
16379
16380     /* [HGM] find reversible plies */
16381     {   int i = 0, j=move;
16382
16383         if (appData.debugMode) { int k;
16384             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16385             for(k=backwardMostMove; k<=forwardMostMove; k++)
16386                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16387
16388         }
16389
16390         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16391         if( j == backwardMostMove ) i += initialRulePlies;
16392         sprintf(p, "%d ", i);
16393         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16394     }
16395     /* Fullmove number */
16396     sprintf(p, "%d", (move / 2) + 1);
16397
16398     return StrSave(buf);
16399 }
16400
16401 Boolean
16402 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16403 {
16404     int i, j;
16405     char *p, c;
16406     int emptycount;
16407     ChessSquare piece;
16408
16409     p = fen;
16410
16411     /* [HGM] by default clear Crazyhouse holdings, if present */
16412     if(gameInfo.holdingsWidth) {
16413        for(i=0; i<BOARD_HEIGHT; i++) {
16414            board[i][0]             = EmptySquare; /* black holdings */
16415            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16416            board[i][1]             = (ChessSquare) 0; /* black counts */
16417            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16418        }
16419     }
16420
16421     /* Piece placement data */
16422     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16423         j = 0;
16424         for (;;) {
16425             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16426                 if (*p == '/') p++;
16427                 emptycount = gameInfo.boardWidth - j;
16428                 while (emptycount--)
16429                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16430                 break;
16431 #if(BOARD_FILES >= 10)
16432             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16433                 p++; emptycount=10;
16434                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16435                 while (emptycount--)
16436                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16437 #endif
16438             } else if (isdigit(*p)) {
16439                 emptycount = *p++ - '0';
16440                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16441                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16442                 while (emptycount--)
16443                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16444             } else if (*p == '+' || isalpha(*p)) {
16445                 if (j >= gameInfo.boardWidth) return FALSE;
16446                 if(*p=='+') {
16447                     piece = CharToPiece(*++p);
16448                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16449                     piece = (ChessSquare) (PROMOTED piece ); p++;
16450                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16451                 } else piece = CharToPiece(*p++);
16452
16453                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16454                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16455                     piece = (ChessSquare) (PROMOTED piece);
16456                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16457                     p++;
16458                 }
16459                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16460             } else {
16461                 return FALSE;
16462             }
16463         }
16464     }
16465     while (*p == '/' || *p == ' ') p++;
16466
16467     /* [HGM] look for Crazyhouse holdings here */
16468     while(*p==' ') p++;
16469     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16470         if(*p == '[') p++;
16471         if(*p == '-' ) p++; /* empty holdings */ else {
16472             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16473             /* if we would allow FEN reading to set board size, we would   */
16474             /* have to add holdings and shift the board read so far here   */
16475             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16476                 p++;
16477                 if((int) piece >= (int) BlackPawn ) {
16478                     i = (int)piece - (int)BlackPawn;
16479                     i = PieceToNumber((ChessSquare)i);
16480                     if( i >= gameInfo.holdingsSize ) return FALSE;
16481                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16482                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16483                 } else {
16484                     i = (int)piece - (int)WhitePawn;
16485                     i = PieceToNumber((ChessSquare)i);
16486                     if( i >= gameInfo.holdingsSize ) return FALSE;
16487                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16488                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16489                 }
16490             }
16491         }
16492         if(*p == ']') p++;
16493     }
16494
16495     while(*p == ' ') p++;
16496
16497     /* Active color */
16498     c = *p++;
16499     if(appData.colorNickNames) {
16500       if( c == appData.colorNickNames[0] ) c = 'w'; else
16501       if( c == appData.colorNickNames[1] ) c = 'b';
16502     }
16503     switch (c) {
16504       case 'w':
16505         *blackPlaysFirst = FALSE;
16506         break;
16507       case 'b':
16508         *blackPlaysFirst = TRUE;
16509         break;
16510       default:
16511         return FALSE;
16512     }
16513
16514     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16515     /* return the extra info in global variiables             */
16516
16517     /* set defaults in case FEN is incomplete */
16518     board[EP_STATUS] = EP_UNKNOWN;
16519     for(i=0; i<nrCastlingRights; i++ ) {
16520         board[CASTLING][i] =
16521             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16522     }   /* assume possible unless obviously impossible */
16523     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16524     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16525     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16526                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16527     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16528     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16529     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16530                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16531     FENrulePlies = 0;
16532
16533     while(*p==' ') p++;
16534     if(nrCastlingRights) {
16535       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16536           /* castling indicator present, so default becomes no castlings */
16537           for(i=0; i<nrCastlingRights; i++ ) {
16538                  board[CASTLING][i] = NoRights;
16539           }
16540       }
16541       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16542              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16543              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16544              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16545         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16546
16547         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16548             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16549             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16550         }
16551         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16552             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16553         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16554                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16555         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16556                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16557         switch(c) {
16558           case'K':
16559               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16560               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16561               board[CASTLING][2] = whiteKingFile;
16562               break;
16563           case'Q':
16564               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16565               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16566               board[CASTLING][2] = whiteKingFile;
16567               break;
16568           case'k':
16569               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16570               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16571               board[CASTLING][5] = blackKingFile;
16572               break;
16573           case'q':
16574               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16575               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16576               board[CASTLING][5] = blackKingFile;
16577           case '-':
16578               break;
16579           default: /* FRC castlings */
16580               if(c >= 'a') { /* black rights */
16581                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16582                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16583                   if(i == BOARD_RGHT) break;
16584                   board[CASTLING][5] = i;
16585                   c -= AAA;
16586                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16587                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16588                   if(c > i)
16589                       board[CASTLING][3] = c;
16590                   else
16591                       board[CASTLING][4] = c;
16592               } else { /* white rights */
16593                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16594                     if(board[0][i] == WhiteKing) break;
16595                   if(i == BOARD_RGHT) break;
16596                   board[CASTLING][2] = i;
16597                   c -= AAA - 'a' + 'A';
16598                   if(board[0][c] >= WhiteKing) break;
16599                   if(c > i)
16600                       board[CASTLING][0] = c;
16601                   else
16602                       board[CASTLING][1] = c;
16603               }
16604         }
16605       }
16606       for(i=0; i<nrCastlingRights; i++)
16607         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16608     if (appData.debugMode) {
16609         fprintf(debugFP, "FEN castling rights:");
16610         for(i=0; i<nrCastlingRights; i++)
16611         fprintf(debugFP, " %d", board[CASTLING][i]);
16612         fprintf(debugFP, "\n");
16613     }
16614
16615       while(*p==' ') p++;
16616     }
16617
16618     /* read e.p. field in games that know e.p. capture */
16619     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16620        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16621       if(*p=='-') {
16622         p++; board[EP_STATUS] = EP_NONE;
16623       } else {
16624          char c = *p++ - AAA;
16625
16626          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16627          if(*p >= '0' && *p <='9') p++;
16628          board[EP_STATUS] = c;
16629       }
16630     }
16631
16632
16633     if(sscanf(p, "%d", &i) == 1) {
16634         FENrulePlies = i; /* 50-move ply counter */
16635         /* (The move number is still ignored)    */
16636     }
16637
16638     return TRUE;
16639 }
16640
16641 void
16642 EditPositionPasteFEN (char *fen)
16643 {
16644   if (fen != NULL) {
16645     Board initial_position;
16646
16647     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16648       DisplayError(_("Bad FEN position in clipboard"), 0);
16649       return ;
16650     } else {
16651       int savedBlackPlaysFirst = blackPlaysFirst;
16652       EditPositionEvent();
16653       blackPlaysFirst = savedBlackPlaysFirst;
16654       CopyBoard(boards[0], initial_position);
16655       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16656       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16657       DisplayBothClocks();
16658       DrawPosition(FALSE, boards[currentMove]);
16659     }
16660   }
16661 }
16662
16663 static char cseq[12] = "\\   ";
16664
16665 Boolean
16666 set_cont_sequence (char *new_seq)
16667 {
16668     int len;
16669     Boolean ret;
16670
16671     // handle bad attempts to set the sequence
16672         if (!new_seq)
16673                 return 0; // acceptable error - no debug
16674
16675     len = strlen(new_seq);
16676     ret = (len > 0) && (len < sizeof(cseq));
16677     if (ret)
16678       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16679     else if (appData.debugMode)
16680       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16681     return ret;
16682 }
16683
16684 /*
16685     reformat a source message so words don't cross the width boundary.  internal
16686     newlines are not removed.  returns the wrapped size (no null character unless
16687     included in source message).  If dest is NULL, only calculate the size required
16688     for the dest buffer.  lp argument indicats line position upon entry, and it's
16689     passed back upon exit.
16690 */
16691 int
16692 wrap (char *dest, char *src, int count, int width, int *lp)
16693 {
16694     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16695
16696     cseq_len = strlen(cseq);
16697     old_line = line = *lp;
16698     ansi = len = clen = 0;
16699
16700     for (i=0; i < count; i++)
16701     {
16702         if (src[i] == '\033')
16703             ansi = 1;
16704
16705         // if we hit the width, back up
16706         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16707         {
16708             // store i & len in case the word is too long
16709             old_i = i, old_len = len;
16710
16711             // find the end of the last word
16712             while (i && src[i] != ' ' && src[i] != '\n')
16713             {
16714                 i--;
16715                 len--;
16716             }
16717
16718             // word too long?  restore i & len before splitting it
16719             if ((old_i-i+clen) >= width)
16720             {
16721                 i = old_i;
16722                 len = old_len;
16723             }
16724
16725             // extra space?
16726             if (i && src[i-1] == ' ')
16727                 len--;
16728
16729             if (src[i] != ' ' && src[i] != '\n')
16730             {
16731                 i--;
16732                 if (len)
16733                     len--;
16734             }
16735
16736             // now append the newline and continuation sequence
16737             if (dest)
16738                 dest[len] = '\n';
16739             len++;
16740             if (dest)
16741                 strncpy(dest+len, cseq, cseq_len);
16742             len += cseq_len;
16743             line = cseq_len;
16744             clen = cseq_len;
16745             continue;
16746         }
16747
16748         if (dest)
16749             dest[len] = src[i];
16750         len++;
16751         if (!ansi)
16752             line++;
16753         if (src[i] == '\n')
16754             line = 0;
16755         if (src[i] == 'm')
16756             ansi = 0;
16757     }
16758     if (dest && appData.debugMode)
16759     {
16760         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16761             count, width, line, len, *lp);
16762         show_bytes(debugFP, src, count);
16763         fprintf(debugFP, "\ndest: ");
16764         show_bytes(debugFP, dest, len);
16765         fprintf(debugFP, "\n");
16766     }
16767     *lp = dest ? line : old_line;
16768
16769     return len;
16770 }
16771
16772 // [HGM] vari: routines for shelving variations
16773 Boolean modeRestore = FALSE;
16774
16775 void
16776 PushInner (int firstMove, int lastMove)
16777 {
16778         int i, j, nrMoves = lastMove - firstMove;
16779
16780         // push current tail of game on stack
16781         savedResult[storedGames] = gameInfo.result;
16782         savedDetails[storedGames] = gameInfo.resultDetails;
16783         gameInfo.resultDetails = NULL;
16784         savedFirst[storedGames] = firstMove;
16785         savedLast [storedGames] = lastMove;
16786         savedFramePtr[storedGames] = framePtr;
16787         framePtr -= nrMoves; // reserve space for the boards
16788         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16789             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16790             for(j=0; j<MOVE_LEN; j++)
16791                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16792             for(j=0; j<2*MOVE_LEN; j++)
16793                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16794             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16795             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16796             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16797             pvInfoList[firstMove+i-1].depth = 0;
16798             commentList[framePtr+i] = commentList[firstMove+i];
16799             commentList[firstMove+i] = NULL;
16800         }
16801
16802         storedGames++;
16803         forwardMostMove = firstMove; // truncate game so we can start variation
16804 }
16805
16806 void
16807 PushTail (int firstMove, int lastMove)
16808 {
16809         if(appData.icsActive) { // only in local mode
16810                 forwardMostMove = currentMove; // mimic old ICS behavior
16811                 return;
16812         }
16813         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16814
16815         PushInner(firstMove, lastMove);
16816         if(storedGames == 1) GreyRevert(FALSE);
16817         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16818 }
16819
16820 void
16821 PopInner (Boolean annotate)
16822 {
16823         int i, j, nrMoves;
16824         char buf[8000], moveBuf[20];
16825
16826         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16827         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16828         nrMoves = savedLast[storedGames] - currentMove;
16829         if(annotate) {
16830                 int cnt = 10;
16831                 if(!WhiteOnMove(currentMove))
16832                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16833                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16834                 for(i=currentMove; i<forwardMostMove; i++) {
16835                         if(WhiteOnMove(i))
16836                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16837                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16838                         strcat(buf, moveBuf);
16839                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16840                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16841                 }
16842                 strcat(buf, ")");
16843         }
16844         for(i=1; i<=nrMoves; i++) { // copy last variation back
16845             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16846             for(j=0; j<MOVE_LEN; j++)
16847                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16848             for(j=0; j<2*MOVE_LEN; j++)
16849                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16850             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16851             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16852             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16853             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16854             commentList[currentMove+i] = commentList[framePtr+i];
16855             commentList[framePtr+i] = NULL;
16856         }
16857         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16858         framePtr = savedFramePtr[storedGames];
16859         gameInfo.result = savedResult[storedGames];
16860         if(gameInfo.resultDetails != NULL) {
16861             free(gameInfo.resultDetails);
16862       }
16863         gameInfo.resultDetails = savedDetails[storedGames];
16864         forwardMostMove = currentMove + nrMoves;
16865 }
16866
16867 Boolean
16868 PopTail (Boolean annotate)
16869 {
16870         if(appData.icsActive) return FALSE; // only in local mode
16871         if(!storedGames) return FALSE; // sanity
16872         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16873
16874         PopInner(annotate);
16875         if(currentMove < forwardMostMove) ForwardEvent(); else
16876         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16877
16878         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16879         return TRUE;
16880 }
16881
16882 void
16883 CleanupTail ()
16884 {       // remove all shelved variations
16885         int i;
16886         for(i=0; i<storedGames; i++) {
16887             if(savedDetails[i])
16888                 free(savedDetails[i]);
16889             savedDetails[i] = NULL;
16890         }
16891         for(i=framePtr; i<MAX_MOVES; i++) {
16892                 if(commentList[i]) free(commentList[i]);
16893                 commentList[i] = NULL;
16894         }
16895         framePtr = MAX_MOVES-1;
16896         storedGames = 0;
16897 }
16898
16899 void
16900 LoadVariation (int index, char *text)
16901 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16902         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16903         int level = 0, move;
16904
16905         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16906         // first find outermost bracketing variation
16907         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16908             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16909                 if(*p == '{') wait = '}'; else
16910                 if(*p == '[') wait = ']'; else
16911                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16912                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16913             }
16914             if(*p == wait) wait = NULLCHAR; // closing ]} found
16915             p++;
16916         }
16917         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16918         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16919         end[1] = NULLCHAR; // clip off comment beyond variation
16920         ToNrEvent(currentMove-1);
16921         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16922         // kludge: use ParsePV() to append variation to game
16923         move = currentMove;
16924         ParsePV(start, TRUE, TRUE);
16925         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16926         ClearPremoveHighlights();
16927         CommentPopDown();
16928         ToNrEvent(currentMove+1);
16929 }
16930